Skip to content

Commit

Permalink
feat: finish it up!
Browse files Browse the repository at this point in the history
  • Loading branch information
naomi-lgbt committed Oct 22, 2024
1 parent f2f6e98 commit 7e61801
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 20 deletions.
2 changes: 2 additions & 0 deletions examples/node-agent-live/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
chatlog.txt
output-*.wav
102 changes: 102 additions & 0 deletions examples/node-agent-live/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const { writeFile, appendFile } = require("fs/promises");
const { createClient, AgentEvents } = require("../../dist/main/index");
const fetch = require("cross-fetch");
const { join } = require("path");

const deepgram = createClient(process.env.DEEPGRAM_API_KEY);

const agent = async () => {
let audioBuffer = Buffer.alloc(0);
let i = 0;
const url = "https://dpgr.am/spacewalk.wav";
const connection = deepgram.agent.live();
connection.on(AgentEvents.Open, async () => {
console.log("Connection opened");

await connection.configure({
audio: {
input: {
encoding: "linear16",
sampleRate: 44100,
},
output: {
encoding: "linear16",
sampleRate: 16000,
container: "wav",
},
},
agent: {
listen: {
model: "nova-2",
},
speak: {
model: "aura-asteria-en",
},
think: {
provider: {
type: "anthropic",
},
model: "claude-3-haiku-20240307",
},
},
});
console.log("Deepgram Agent configured.");

setInterval(() => {
console.log("Keep alive!");
void connection.keepAlive();
}, 5000);

fetch(url)
.then((r) => r.body)
.then((res) => {
res.on("readable", () => {
connection.send(res.read());
});
});
});

connection.on(AgentEvents.Close, () => {
console.log("Connection closed");
process.exit(0);
});

connection.on(AgentEvents.ConversationText, async (data) => {
await appendFile(join(__dirname, `chatlog.txt`), JSON.stringify(data) + "\n");
});

connection.on(AgentEvents.UserStartedSpeaking, () => {
if (audioBuffer.length) {
console.log("Interrupting agent.");
audioBuffer = Buffer.alloc(0);
}
});

connection.on(AgentEvents.Metadata, (data) => {
console.dir(data, { depth: null });
});

connection.on(AgentEvents.Audio, (data) => {
// Concatenate the audio chunks into a single buffer
const buffer = Buffer.from(data);
audioBuffer = Buffer.concat([audioBuffer, buffer]);
});

connection.on(AgentEvents.Error, (err) => {
console.error("Error!");
console.error(err);
console.error(err.message);
});

connection.on(AgentEvents.AgentAudioDone, async () => {
await writeFile(join(__dirname, `output-${i}.wav`), audioBuffer);
audioBuffer = Buffer.alloc(0);
i++;
});

connection.on(AgentEvents.Unhandled, (data) => {
console.dir(data, { depth: null });
});
};

void agent();
11 changes: 11 additions & 0 deletions src/DeepgramClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SelfHostedRestClient,
SpeakClient,
ModelsRestClient,
AgentClient,
} from "./packages";

/**
Expand Down Expand Up @@ -80,6 +81,16 @@ export default class DeepgramClient extends AbstractClient {
return new SpeakClient(this.options);
}

/**
* Returns a new instance of the AgentClient, which provides access to Deepgram's Voice Agent API.
*
* @returns {AgentClient} A new instance of the AgentClient.
* @beta
*/
get agent(): any {
return new AgentClient(this.options);
}

/**
* @deprecated
* @see https://dpgr.am/js-v3
Expand Down
4 changes: 4 additions & 0 deletions src/lib/enums/AgentEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export enum AgentEvents {
Open = "Open",
Close = "Close",
Error = "Error",
/**
* Audio event?
*/
Audio = "Audio",
/**
* Message { type: string }
*/
Expand Down
1 change: 1 addition & 0 deletions src/lib/enums/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./AgentEvents";
export * from "./LiveConnectionState";
export * from "./LiveTranscriptionEvents";
export * from "./LiveTTSEvents";
4 changes: 2 additions & 2 deletions src/packages/AbstractLiveClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ export abstract class AbstractLiveClient extends AbstractClient {
}

if (typeof data !== "string") {
if (data.byteLength === 0) {
this.log("warn", "skipping `send` for zero-byte blob", data);
if (!data?.byteLength) {
this.log("warn", "skipping `send` for zero-byte payload", data);

return;
}
Expand Down
21 changes: 21 additions & 0 deletions src/packages/AgentClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AbstractClient } from "./AbstractClient";
import { AgentLiveClient } from "./AgentLiveClient";

/**
* The `AgentClient` class extends the `AbstractClient` class and provides access to the "agent" namespace.
* It exposes one method:
*
* `live(ttsOptions: SpeakSchema = {}, endpoint = ":version/speak")`: Returns an `AgentLiveClient` instance for interacting with the voice agent API.
*/
export class AgentClient extends AbstractClient {
public namespace: string = "speak";

/**
* Returns an `AgentLiveClient` instance for interacting with the voice agent API.
* @param {string} [endpoint="/agent"] - The endpoint to use for the live speak API.
* @returns {SpeakLiveClient} - A `SpeakLiveClient` instance for interacting with the live speak API.
*/
public live(endpoint: string = "/agent"): AgentLiveClient {
return new AgentLiveClient(this.options, endpoint);
}
}
73 changes: 56 additions & 17 deletions src/packages/AgentLiveClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AgentEvents } from "../lib/enums/AgentEvents.js";
import { AgentEvents } from "../lib/enums/AgentEvents";
import type { AgentLiveSchema, SpeakModel } from "../lib/types";
import type { DeepgramClientOptions } from "../lib/types";
import { AbstractLiveClient } from "./AbstractLiveClient";

export class AgentLiveClient extends AbstractLiveClient {
public namespace: string = "agent";

constructor(options: DeepgramClientOptions, endpoint: string = ":version/agent") {
constructor(options: DeepgramClientOptions, endpoint: string = "/agent") {
super(options);
/**
* According to the docs, this is the correct base URL for the Agent API.
Expand Down Expand Up @@ -44,25 +44,64 @@ export class AgentLiveClient extends AbstractLiveClient {
};

this.conn.onmessage = (event: MessageEvent) => {
try {
const data: any = JSON.parse(event.data.toString());

if (data.type in AgentEvents) {
this.emit(data.type, data);
} else {
this.emit(AgentEvents.Unhandled, data);
}
} catch (error) {
this.emit(AgentEvents.Error, {
event,
message: "Unable to parse `data` as JSON.",
error,
});
}
this.handleMessage(event);
};
}
}

/**
* Handles incoming messages from the WebSocket connection.
* @param event - The MessageEvent object representing the received message.
*/
protected handleMessage(event: MessageEvent): void {
if (typeof event.data === "string") {
try {
const data = JSON.parse(event.data);
this.handleTextMessage(data);
} catch (error) {
this.emit(AgentEvents.Error, {
event,
message: "Unable to parse `data` as JSON.",
error,
});
}
} else if (event.data instanceof Blob) {
event.data.arrayBuffer().then((buffer) => {
this.handleBinaryMessage(Buffer.from(buffer));
});
} else if (event.data instanceof ArrayBuffer) {
this.handleBinaryMessage(Buffer.from(event.data));
} else if (Buffer.isBuffer(event.data)) {
this.handleBinaryMessage(event.data);
} else {
console.log("Received unknown data type", event.data);
this.emit(AgentEvents.Error, {
event,
message: "Received unknown data type.",
});
}
}

/**
* Handles binary messages received from the WebSocket connection.
* @param data - The binary data.
*/
protected handleBinaryMessage(data: Buffer): void {
this.emit(AgentEvents.Audio, data);
}

/**
* Handles text messages received from the WebSocket connection.
* @param data - The parsed JSON data.
*/
protected handleTextMessage(data: any): void {
if (data.type in AgentEvents) {
this.emit(data.type, data);
} else {
this.emit(AgentEvents.Unhandled, data);
}
}

/**
* To be called with your model configuration BEFORE sending
* any audio data.
Expand Down
2 changes: 1 addition & 1 deletion src/packages/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from "./AbstractClient";
export * from "./AbstractLiveClient";
export * from "./AbstractRestClient";
export * from "./AgentLiveClient";
export * from "./AgentClient";
export * from "./ListenClient";
export * from "./ListenLiveClient";
export * from "./ListenRestClient";
Expand Down

0 comments on commit 7e61801

Please sign in to comment.