@livekit/agents-plugin-openai 1.0.50 → 1.0.51
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.
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/llm.test.cjs +31 -16
- package/dist/llm.test.cjs.map +1 -1
- package/dist/llm.test.js +32 -17
- package/dist/llm.test.js.map +1 -1
- package/dist/responses/llm.cjs +71 -16
- package/dist/responses/llm.cjs.map +1 -1
- package/dist/responses/llm.d.cts +10 -25
- package/dist/responses/llm.d.ts +10 -25
- package/dist/responses/llm.d.ts.map +1 -1
- package/dist/responses/llm.js +71 -14
- package/dist/responses/llm.js.map +1 -1
- package/dist/responses/llm.test.cjs +32 -17
- package/dist/responses/llm.test.cjs.map +1 -1
- package/dist/responses/llm.test.js +33 -18
- package/dist/responses/llm.test.js.map +1 -1
- package/dist/stt.cjs +7 -3
- package/dist/stt.cjs.map +1 -1
- package/dist/stt.d.ts.map +1 -1
- package/dist/stt.js +8 -4
- package/dist/stt.js.map +1 -1
- package/dist/stt.test.cjs +11 -3
- package/dist/stt.test.cjs.map +1 -1
- package/dist/stt.test.js +12 -4
- package/dist/stt.test.js.map +1 -1
- package/dist/tts.test.cjs +11 -3
- package/dist/tts.test.cjs.map +1 -1
- package/dist/tts.test.js +12 -4
- package/dist/tts.test.js.map +1 -1
- package/dist/ws/index.cjs +29 -0
- package/dist/ws/index.cjs.map +1 -0
- package/dist/ws/index.d.cts +3 -0
- package/dist/ws/index.d.ts +3 -0
- package/dist/ws/index.d.ts.map +1 -0
- package/dist/ws/index.js +5 -0
- package/dist/ws/index.js.map +1 -0
- package/dist/ws/llm.cjs +502 -0
- package/dist/ws/llm.cjs.map +1 -0
- package/dist/ws/llm.d.cts +74 -0
- package/dist/ws/llm.d.ts +74 -0
- package/dist/ws/llm.d.ts.map +1 -0
- package/dist/ws/llm.js +485 -0
- package/dist/ws/llm.js.map +1 -0
- package/dist/ws/llm.test.cjs +26 -0
- package/dist/ws/llm.test.cjs.map +1 -0
- package/dist/ws/llm.test.d.cts +2 -0
- package/dist/ws/llm.test.d.ts +2 -0
- package/dist/ws/llm.test.d.ts.map +1 -0
- package/dist/ws/llm.test.js +25 -0
- package/dist/ws/llm.test.js.map +1 -0
- package/dist/ws/types.cjs +128 -0
- package/dist/ws/types.cjs.map +1 -0
- package/dist/ws/types.d.cts +167 -0
- package/dist/ws/types.d.ts +167 -0
- package/dist/ws/types.d.ts.map +1 -0
- package/dist/ws/types.js +95 -0
- package/dist/ws/types.js.map +1 -0
- package/package.json +6 -5
- package/src/index.ts +1 -0
- package/src/llm.test.ts +31 -17
- package/src/responses/llm.test.ts +32 -18
- package/src/responses/llm.ts +105 -19
- package/src/stt.test.ts +12 -4
- package/src/stt.ts +8 -4
- package/src/tts.test.ts +12 -4
- package/src/ws/index.ts +17 -0
- package/src/ws/llm.test.ts +30 -0
- package/src/ws/llm.ts +665 -0
- package/src/ws/types.ts +131 -0
package/dist/ws/types.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const wsResponseCreateEventSchema = z.object({
|
|
3
|
+
type: z.literal("response.create"),
|
|
4
|
+
model: z.string(),
|
|
5
|
+
input: z.array(z.unknown()),
|
|
6
|
+
tools: z.array(z.unknown()).optional(),
|
|
7
|
+
previous_response_id: z.string().nullable().optional(),
|
|
8
|
+
store: z.boolean().optional(),
|
|
9
|
+
temperature: z.number().optional(),
|
|
10
|
+
metadata: z.record(z.string(), z.string()).optional()
|
|
11
|
+
}).passthrough();
|
|
12
|
+
const wsResponseCreatedEventSchema = z.object({
|
|
13
|
+
type: z.literal("response.created"),
|
|
14
|
+
response: z.object({
|
|
15
|
+
id: z.string()
|
|
16
|
+
}).passthrough()
|
|
17
|
+
});
|
|
18
|
+
const wsFunctionCallItemSchema = z.object({
|
|
19
|
+
type: z.literal("function_call"),
|
|
20
|
+
call_id: z.string(),
|
|
21
|
+
name: z.string(),
|
|
22
|
+
arguments: z.string()
|
|
23
|
+
});
|
|
24
|
+
const wsOutputItemSchema = z.discriminatedUnion("type", [
|
|
25
|
+
wsFunctionCallItemSchema,
|
|
26
|
+
z.object({ type: z.literal("message") }).passthrough(),
|
|
27
|
+
z.object({ type: z.literal("reasoning") }).passthrough(),
|
|
28
|
+
z.object({ type: z.literal("file") }).passthrough(),
|
|
29
|
+
z.object({ type: z.literal("computer_call") }).passthrough(),
|
|
30
|
+
z.object({ type: z.literal("web_search_call") }).passthrough()
|
|
31
|
+
]);
|
|
32
|
+
const wsOutputItemDoneEventSchema = z.object({
|
|
33
|
+
type: z.literal("response.output_item.done"),
|
|
34
|
+
item: wsOutputItemSchema
|
|
35
|
+
});
|
|
36
|
+
const wsOutputTextDeltaEventSchema = z.object({
|
|
37
|
+
type: z.literal("response.output_text.delta"),
|
|
38
|
+
delta: z.string()
|
|
39
|
+
});
|
|
40
|
+
const wsResponseCompletedEventSchema = z.object({
|
|
41
|
+
type: z.literal("response.completed"),
|
|
42
|
+
response: z.object({
|
|
43
|
+
id: z.string(),
|
|
44
|
+
usage: z.object({
|
|
45
|
+
output_tokens: z.number(),
|
|
46
|
+
input_tokens: z.number(),
|
|
47
|
+
total_tokens: z.number(),
|
|
48
|
+
input_tokens_details: z.object({
|
|
49
|
+
cached_tokens: z.number()
|
|
50
|
+
}).passthrough()
|
|
51
|
+
}).optional()
|
|
52
|
+
}).passthrough()
|
|
53
|
+
});
|
|
54
|
+
const wsResponseFailedEventSchema = z.object({
|
|
55
|
+
type: z.literal("response.failed"),
|
|
56
|
+
response: z.object({
|
|
57
|
+
id: z.string().optional(),
|
|
58
|
+
error: z.object({
|
|
59
|
+
code: z.string().optional(),
|
|
60
|
+
message: z.string().optional()
|
|
61
|
+
}).optional()
|
|
62
|
+
}).passthrough()
|
|
63
|
+
});
|
|
64
|
+
const wsErrorEventSchema = z.object({
|
|
65
|
+
type: z.literal("error"),
|
|
66
|
+
status: z.number().optional(),
|
|
67
|
+
error: z.object({
|
|
68
|
+
type: z.string().optional(),
|
|
69
|
+
code: z.string().optional(),
|
|
70
|
+
message: z.string().optional(),
|
|
71
|
+
param: z.string().optional()
|
|
72
|
+
}).optional(),
|
|
73
|
+
message: z.string().optional()
|
|
74
|
+
});
|
|
75
|
+
const wsServerEventSchema = z.discriminatedUnion("type", [
|
|
76
|
+
wsResponseCreatedEventSchema,
|
|
77
|
+
wsOutputItemDoneEventSchema,
|
|
78
|
+
wsOutputTextDeltaEventSchema,
|
|
79
|
+
wsResponseCompletedEventSchema,
|
|
80
|
+
wsResponseFailedEventSchema,
|
|
81
|
+
wsErrorEventSchema
|
|
82
|
+
]);
|
|
83
|
+
export {
|
|
84
|
+
wsErrorEventSchema,
|
|
85
|
+
wsFunctionCallItemSchema,
|
|
86
|
+
wsOutputItemDoneEventSchema,
|
|
87
|
+
wsOutputItemSchema,
|
|
88
|
+
wsOutputTextDeltaEventSchema,
|
|
89
|
+
wsResponseCompletedEventSchema,
|
|
90
|
+
wsResponseCreateEventSchema,
|
|
91
|
+
wsResponseCreatedEventSchema,
|
|
92
|
+
wsResponseFailedEventSchema,
|
|
93
|
+
wsServerEventSchema
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/ws/types.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { z } from 'zod';\n\n// ============================================================================\n// Client → Server events\n// ============================================================================\n\nexport const wsResponseCreateEventSchema = z\n .object({\n type: z.literal('response.create'),\n model: z.string(),\n input: z.array(z.unknown()),\n tools: z.array(z.unknown()).optional(),\n previous_response_id: z.string().nullable().optional(),\n store: z.boolean().optional(),\n temperature: z.number().optional(),\n metadata: z.record(z.string(), z.string()).optional(),\n })\n .passthrough();\n\nexport type WsResponseCreateEvent = z.infer<typeof wsResponseCreateEventSchema>;\n\n// ============================================================================\n// Server → Client events\n// ============================================================================\n\nexport const wsResponseCreatedEventSchema = z.object({\n type: z.literal('response.created'),\n response: z\n .object({\n id: z.string(),\n })\n .passthrough(),\n});\n\nexport const wsFunctionCallItemSchema = z.object({\n type: z.literal('function_call'),\n call_id: z.string(),\n name: z.string(),\n arguments: z.string(),\n});\n\nexport const wsOutputItemSchema = z.discriminatedUnion('type', [\n wsFunctionCallItemSchema,\n z.object({ type: z.literal('message') }).passthrough(),\n z.object({ type: z.literal('reasoning') }).passthrough(),\n z.object({ type: z.literal('file') }).passthrough(),\n z.object({ type: z.literal('computer_call') }).passthrough(),\n z.object({ type: z.literal('web_search_call') }).passthrough(),\n]);\n\nexport const wsOutputItemDoneEventSchema = z.object({\n type: z.literal('response.output_item.done'),\n item: wsOutputItemSchema,\n});\n\nexport const wsOutputTextDeltaEventSchema = z.object({\n type: z.literal('response.output_text.delta'),\n delta: z.string(),\n});\n\nexport const wsResponseCompletedEventSchema = z.object({\n type: z.literal('response.completed'),\n response: z\n .object({\n id: z.string(),\n usage: z\n .object({\n output_tokens: z.number(),\n input_tokens: z.number(),\n total_tokens: z.number(),\n input_tokens_details: z\n .object({\n cached_tokens: z.number(),\n })\n .passthrough(),\n })\n .optional(),\n })\n .passthrough(),\n});\n\nexport const wsResponseFailedEventSchema = z.object({\n type: z.literal('response.failed'),\n response: z\n .object({\n id: z.string().optional(),\n error: z\n .object({\n code: z.string().optional(),\n message: z.string().optional(),\n })\n .optional(),\n })\n .passthrough(),\n});\n\nexport const wsErrorEventSchema = z.object({\n type: z.literal('error'),\n status: z.number().optional(),\n error: z\n .object({\n type: z.string().optional(),\n code: z.string().optional(),\n message: z.string().optional(),\n param: z.string().optional(),\n })\n .optional(),\n message: z.string().optional(),\n});\n\nexport const wsServerEventSchema = z.discriminatedUnion('type', [\n wsResponseCreatedEventSchema,\n wsOutputItemDoneEventSchema,\n wsOutputTextDeltaEventSchema,\n wsResponseCompletedEventSchema,\n wsResponseFailedEventSchema,\n wsErrorEventSchema,\n]);\n\nexport type WsResponseCreatedEvent = z.infer<typeof wsResponseCreatedEventSchema>;\nexport type WsFunctionCallItem = z.infer<typeof wsFunctionCallItemSchema>;\nexport type WsOutputItem = z.infer<typeof wsOutputItemSchema>;\nexport type WsOutputItemDoneEvent = z.infer<typeof wsOutputItemDoneEventSchema>;\nexport type WsOutputTextDeltaEvent = z.infer<typeof wsOutputTextDeltaEventSchema>;\nexport type WsResponseCompletedEvent = z.infer<typeof wsResponseCompletedEventSchema>;\nexport type WsResponseFailedEvent = z.infer<typeof wsResponseFailedEventSchema>;\nexport type WsErrorEvent = z.infer<typeof wsErrorEventSchema>;\nexport type WsServerEvent = z.infer<typeof wsServerEventSchema>;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/agents-plugin-openai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.51",
|
|
4
4
|
"description": "OpenAI plugin for LiveKit Node Agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@types/ws": "^8.5.10",
|
|
31
31
|
"tsup": "^8.3.5",
|
|
32
32
|
"typescript": "^5.0.0",
|
|
33
|
-
"@livekit/agents": "1.0.
|
|
34
|
-
"@livekit/agents-plugin-silero": "1.0.
|
|
35
|
-
"@livekit/agents-plugins-test": "1.0.
|
|
33
|
+
"@livekit/agents": "1.0.51",
|
|
34
|
+
"@livekit/agents-plugin-silero": "1.0.51",
|
|
35
|
+
"@livekit/agents-plugins-test": "1.0.51"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@livekit/mutex": "^1.1.1",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@livekit/rtc-node": "^0.13.24",
|
|
44
|
-
"
|
|
44
|
+
"zod": "^3.25.76 || ^4.1.8",
|
|
45
|
+
"@livekit/agents": "1.0.51"
|
|
45
46
|
},
|
|
46
47
|
"scripts": {
|
|
47
48
|
"build": "tsup --onSuccess \"pnpm build:types\"",
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * as realtime from './realtime/index.js';
|
|
|
9
9
|
export * as responses from './responses/index.js';
|
|
10
10
|
export { STT, type STTOptions } from './stt.js';
|
|
11
11
|
export { ChunkedStream, TTS, type TTSOptions } from './tts.js';
|
|
12
|
+
export * as ws from './ws/index.js';
|
|
12
13
|
|
|
13
14
|
class OpenAIPlugin extends Plugin {
|
|
14
15
|
constructor() {
|
package/src/llm.test.ts
CHANGED
|
@@ -2,23 +2,37 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { llm, llmStrict } from '@livekit/agents-plugins-test';
|
|
5
|
-
import { describe } from 'vitest';
|
|
5
|
+
import { describe, it } from 'vitest';
|
|
6
6
|
import { LLM } from './llm.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
await llm(
|
|
10
|
-
new LLM({
|
|
11
|
-
temperature: 0,
|
|
12
|
-
}),
|
|
13
|
-
false,
|
|
14
|
-
);
|
|
15
|
-
});
|
|
8
|
+
const hasOpenAIApiKey = Boolean(process.env.OPENAI_API_KEY);
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
if (hasOpenAIApiKey) {
|
|
11
|
+
describe('OpenAI', async () => {
|
|
12
|
+
await llm(
|
|
13
|
+
new LLM({
|
|
14
|
+
temperature: 0,
|
|
15
|
+
}),
|
|
16
|
+
false,
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
} else {
|
|
20
|
+
describe('OpenAI', () => {
|
|
21
|
+
it.skip('requires OPENAI_API_KEY', () => {});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (hasOpenAIApiKey) {
|
|
26
|
+
describe('OpenAI strict tool schema', async () => {
|
|
27
|
+
await llmStrict(
|
|
28
|
+
new LLM({
|
|
29
|
+
temperature: 0,
|
|
30
|
+
strictToolSchema: true,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
describe('OpenAI strict tool schema', () => {
|
|
36
|
+
it.skip('requires OPENAI_API_KEY', () => {});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -2,24 +2,38 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { llm, llmStrict } from '@livekit/agents-plugins-test';
|
|
5
|
-
import { describe } from 'vitest';
|
|
5
|
+
import { describe, it } from 'vitest';
|
|
6
6
|
import { LLM } from './llm.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
await llm(
|
|
10
|
-
new LLM({
|
|
11
|
-
temperature: 0,
|
|
12
|
-
strictToolSchema: false,
|
|
13
|
-
}),
|
|
14
|
-
true,
|
|
15
|
-
);
|
|
16
|
-
});
|
|
8
|
+
const hasOpenAIApiKey = Boolean(process.env.OPENAI_API_KEY);
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
10
|
+
if (hasOpenAIApiKey) {
|
|
11
|
+
describe('OpenAI Responses', async () => {
|
|
12
|
+
await llm(
|
|
13
|
+
new LLM({
|
|
14
|
+
temperature: 0,
|
|
15
|
+
strictToolSchema: false,
|
|
16
|
+
}),
|
|
17
|
+
true,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
describe('OpenAI Responses', () => {
|
|
22
|
+
it.skip('requires OPENAI_API_KEY', () => {});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (hasOpenAIApiKey) {
|
|
27
|
+
describe('OpenAI Responses strict tool schema', async () => {
|
|
28
|
+
await llmStrict(
|
|
29
|
+
new LLM({
|
|
30
|
+
temperature: 0,
|
|
31
|
+
strictToolSchema: true,
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
describe('OpenAI Responses strict tool schema', () => {
|
|
37
|
+
it.skip('requires OPENAI_API_KEY', () => {});
|
|
38
|
+
});
|
|
39
|
+
}
|
package/src/responses/llm.ts
CHANGED
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
APITimeoutError,
|
|
9
9
|
DEFAULT_API_CONNECT_OPTIONS,
|
|
10
10
|
llm,
|
|
11
|
+
log,
|
|
11
12
|
toError,
|
|
12
13
|
} from '@livekit/agents';
|
|
13
14
|
import OpenAI from 'openai';
|
|
14
15
|
import type { ChatModels } from '../models.js';
|
|
16
|
+
import { WSLLM } from '../ws/llm.js';
|
|
15
17
|
|
|
16
|
-
interface LLMOptions {
|
|
18
|
+
export interface LLMOptions {
|
|
17
19
|
model: string | ChatModels;
|
|
18
20
|
apiKey?: string;
|
|
19
21
|
baseURL?: string;
|
|
@@ -24,30 +26,32 @@ interface LLMOptions {
|
|
|
24
26
|
store?: boolean;
|
|
25
27
|
metadata?: Record<string, string>;
|
|
26
28
|
strictToolSchema?: boolean;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether to use the WebSocket API.
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
useWebSocket?: boolean;
|
|
27
35
|
}
|
|
28
36
|
|
|
37
|
+
type HttpLLMOptions = Omit<LLMOptions, 'useWebSocket'>;
|
|
38
|
+
|
|
29
39
|
const defaultLLMOptions: LLMOptions = {
|
|
30
40
|
model: 'gpt-4.1',
|
|
31
41
|
apiKey: process.env.OPENAI_API_KEY,
|
|
32
42
|
strictToolSchema: true,
|
|
43
|
+
useWebSocket: true,
|
|
33
44
|
};
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
class ResponsesHttpLLM extends llm.LLM {
|
|
36
47
|
#client: OpenAI;
|
|
37
|
-
#opts:
|
|
48
|
+
#opts: HttpLLMOptions;
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
* Create a new instance of OpenAI Responses LLM.
|
|
41
|
-
*
|
|
42
|
-
* @remarks
|
|
43
|
-
* `apiKey` must be set to your OpenAI API key, either using the argument or by setting the
|
|
44
|
-
* `OPENAI_API_KEY` environment variable.
|
|
45
|
-
*/
|
|
46
|
-
constructor(opts: Partial<LLMOptions> = defaultLLMOptions) {
|
|
50
|
+
constructor(opts: Partial<HttpLLMOptions> = defaultLLMOptions) {
|
|
47
51
|
super();
|
|
48
52
|
|
|
49
53
|
this.#opts = { ...defaultLLMOptions, ...opts };
|
|
50
|
-
if (this.#opts.apiKey === undefined) {
|
|
54
|
+
if (this.#opts.apiKey === undefined && this.#opts.client === undefined) {
|
|
51
55
|
throw new Error('OpenAI API key is required, whether as an argument or as $OPENAI_API_KEY');
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -59,15 +63,15 @@ export class LLM extends llm.LLM {
|
|
|
59
63
|
});
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
label(): string {
|
|
66
|
+
override label(): string {
|
|
63
67
|
return 'openai.responses.LLM';
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
get model(): string {
|
|
70
|
+
override get model(): string {
|
|
67
71
|
return this.#opts.model;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
chat({
|
|
74
|
+
override chat({
|
|
71
75
|
chatCtx,
|
|
72
76
|
toolCtx,
|
|
73
77
|
connOptions = DEFAULT_API_CONNECT_OPTIONS,
|
|
@@ -81,7 +85,7 @@ export class LLM extends llm.LLM {
|
|
|
81
85
|
parallelToolCalls?: boolean;
|
|
82
86
|
toolChoice?: llm.ToolChoice;
|
|
83
87
|
extraKwargs?: Record<string, unknown>;
|
|
84
|
-
}):
|
|
88
|
+
}): ResponsesHttpLLMStream {
|
|
85
89
|
const modelOptions: Record<string, unknown> = { ...(extraKwargs || {}) };
|
|
86
90
|
|
|
87
91
|
parallelToolCalls =
|
|
@@ -110,7 +114,7 @@ export class LLM extends llm.LLM {
|
|
|
110
114
|
modelOptions.metadata = this.#opts.metadata;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
return new
|
|
117
|
+
return new ResponsesHttpLLMStream(this, {
|
|
114
118
|
model: this.#opts.model,
|
|
115
119
|
client: this.#client,
|
|
116
120
|
chatCtx,
|
|
@@ -122,7 +126,7 @@ export class LLM extends llm.LLM {
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
class ResponsesHttpLLMStream extends llm.LLMStream {
|
|
126
130
|
private model: string | ChatModels;
|
|
127
131
|
private client: OpenAI;
|
|
128
132
|
private modelOptions: Record<string, unknown>;
|
|
@@ -130,7 +134,7 @@ export class LLMStream extends llm.LLMStream {
|
|
|
130
134
|
private responseId: string;
|
|
131
135
|
|
|
132
136
|
constructor(
|
|
133
|
-
llm:
|
|
137
|
+
llm: ResponsesHttpLLM,
|
|
134
138
|
{
|
|
135
139
|
model,
|
|
136
140
|
client,
|
|
@@ -325,3 +329,85 @@ export class LLMStream extends llm.LLMStream {
|
|
|
325
329
|
return undefined;
|
|
326
330
|
}
|
|
327
331
|
}
|
|
332
|
+
|
|
333
|
+
export class LLM extends llm.LLM {
|
|
334
|
+
#opts: LLMOptions;
|
|
335
|
+
#llm: llm.LLM;
|
|
336
|
+
#logger = log();
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Create a new instance of OpenAI Responses LLM.
|
|
340
|
+
*
|
|
341
|
+
* @remarks
|
|
342
|
+
* `apiKey` must be set to your OpenAI API key, either using the argument or by setting the
|
|
343
|
+
* `OPENAI_API_KEY` environment variable.
|
|
344
|
+
*/
|
|
345
|
+
constructor(opts: Partial<LLMOptions> = defaultLLMOptions) {
|
|
346
|
+
super();
|
|
347
|
+
|
|
348
|
+
this.#opts = { ...defaultLLMOptions, ...opts };
|
|
349
|
+
const { useWebSocket, client, ...baseOpts } = this.#opts;
|
|
350
|
+
|
|
351
|
+
if (useWebSocket) {
|
|
352
|
+
if (client !== undefined) {
|
|
353
|
+
this.#logger.warn(
|
|
354
|
+
'WebSocket mode does not support custom client; provided client will be ignored',
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
this.#llm = new WSLLM(baseOpts);
|
|
358
|
+
} else {
|
|
359
|
+
this.#llm = new ResponsesHttpLLM({ ...baseOpts, client });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Forward events from the inner delegate so consumers listening on this
|
|
363
|
+
// wrapper instance (e.g. AgentActivity) receive them.
|
|
364
|
+
this.#llm.on('metrics_collected', (metrics) => this.emit('metrics_collected', metrics));
|
|
365
|
+
this.#llm.on('error', (error) => this.emit('error', error));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
override label(): string {
|
|
369
|
+
return this.#llm.label();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
override get model(): string {
|
|
373
|
+
return this.#llm.model;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
override prewarm(): void {
|
|
377
|
+
this.#llm.prewarm();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Ref: python livekit-plugins/livekit-plugins-openai/livekit/plugins/openai/responses/llm.py - 229-233 lines
|
|
381
|
+
override async aclose(): Promise<void> {
|
|
382
|
+
await this.#llm.aclose();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async close(): Promise<void> {
|
|
386
|
+
await this.aclose();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
override chat({
|
|
390
|
+
chatCtx,
|
|
391
|
+
toolCtx,
|
|
392
|
+
connOptions = DEFAULT_API_CONNECT_OPTIONS,
|
|
393
|
+
parallelToolCalls,
|
|
394
|
+
toolChoice,
|
|
395
|
+
extraKwargs,
|
|
396
|
+
}: {
|
|
397
|
+
chatCtx: llm.ChatContext;
|
|
398
|
+
toolCtx?: llm.ToolContext;
|
|
399
|
+
connOptions?: APIConnectOptions;
|
|
400
|
+
parallelToolCalls?: boolean;
|
|
401
|
+
toolChoice?: llm.ToolChoice;
|
|
402
|
+
extraKwargs?: Record<string, unknown>;
|
|
403
|
+
}): llm.LLMStream {
|
|
404
|
+
return this.#llm.chat({
|
|
405
|
+
chatCtx,
|
|
406
|
+
toolCtx,
|
|
407
|
+
connOptions,
|
|
408
|
+
parallelToolCalls,
|
|
409
|
+
toolChoice,
|
|
410
|
+
extraKwargs,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
package/src/stt.test.ts
CHANGED
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { VAD } from '@livekit/agents-plugin-silero';
|
|
5
5
|
import { stt } from '@livekit/agents-plugins-test';
|
|
6
|
-
import { describe } from 'vitest';
|
|
6
|
+
import { describe, it } from 'vitest';
|
|
7
7
|
import { STT } from './stt.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const hasOpenAIApiKey = Boolean(process.env.OPENAI_API_KEY);
|
|
10
|
+
|
|
11
|
+
if (hasOpenAIApiKey) {
|
|
12
|
+
describe('OpenAI', async () => {
|
|
13
|
+
await stt(new STT(), await VAD.load(), { streaming: false });
|
|
14
|
+
});
|
|
15
|
+
} else {
|
|
16
|
+
describe('OpenAI', () => {
|
|
17
|
+
it.skip('requires OPENAI_API_KEY', () => {});
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/stt.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import { type AudioBuffer, mergeFrames, stt } from '@livekit/agents';
|
|
4
|
+
import { type AudioBuffer, mergeFrames, normalizeLanguage, stt } from '@livekit/agents';
|
|
5
5
|
import type { AudioFrame } from '@livekit/rtc-node';
|
|
6
6
|
import { OpenAI } from 'openai';
|
|
7
7
|
import type { GroqAudioModels, WhisperModels } from './models.js';
|
|
@@ -38,7 +38,11 @@ export class STT extends stt.STT {
|
|
|
38
38
|
constructor(opts: Partial<STTOptions> = defaultSTTOptions) {
|
|
39
39
|
super({ streaming: false, interimResults: false, alignedTranscript: false });
|
|
40
40
|
|
|
41
|
-
this.#opts = {
|
|
41
|
+
this.#opts = {
|
|
42
|
+
...defaultSTTOptions,
|
|
43
|
+
...opts,
|
|
44
|
+
language: normalizeLanguage(opts.language ?? defaultSTTOptions.language),
|
|
45
|
+
};
|
|
42
46
|
if (this.#opts.apiKey === undefined) {
|
|
43
47
|
throw new Error('OpenAI API key is required, whether as an argument or as $OPENAI_API_KEY');
|
|
44
48
|
}
|
|
@@ -113,7 +117,7 @@ export class STT extends stt.STT {
|
|
|
113
117
|
|
|
114
118
|
#sanitizeOptions(language?: string): STTOptions {
|
|
115
119
|
if (language) {
|
|
116
|
-
return { ...this.#opts, language };
|
|
120
|
+
return { ...this.#opts, language: normalizeLanguage(language) };
|
|
117
121
|
} else {
|
|
118
122
|
return this.#opts;
|
|
119
123
|
}
|
|
@@ -165,7 +169,7 @@ export class STT extends stt.STT {
|
|
|
165
169
|
alternatives: [
|
|
166
170
|
{
|
|
167
171
|
text: resp.text || '',
|
|
168
|
-
language: config.language || '',
|
|
172
|
+
language: normalizeLanguage(config.language || ''),
|
|
169
173
|
startTime: 0,
|
|
170
174
|
endTime: 0,
|
|
171
175
|
confidence: 0,
|
package/src/tts.test.ts
CHANGED
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { tts } from '@livekit/agents-plugins-test';
|
|
5
|
-
import { describe } from 'vitest';
|
|
5
|
+
import { describe, it } from 'vitest';
|
|
6
6
|
import { STT } from './stt.js';
|
|
7
7
|
import { TTS } from './tts.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const hasOpenAIApiKey = Boolean(process.env.OPENAI_API_KEY);
|
|
10
|
+
|
|
11
|
+
if (hasOpenAIApiKey) {
|
|
12
|
+
describe('OpenAI', async () => {
|
|
13
|
+
await tts(new TTS(), new STT(), { streaming: false });
|
|
14
|
+
});
|
|
15
|
+
} else {
|
|
16
|
+
describe('OpenAI', () => {
|
|
17
|
+
it.skip('requires OPENAI_API_KEY', () => {});
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/ws/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
export { ResponsesWebSocket } from './llm.js';
|
|
6
|
+
export type {
|
|
7
|
+
WsErrorEvent,
|
|
8
|
+
WsFunctionCallItem,
|
|
9
|
+
WsOutputItem,
|
|
10
|
+
WsOutputItemDoneEvent,
|
|
11
|
+
WsOutputTextDeltaEvent,
|
|
12
|
+
WsResponseCompletedEvent,
|
|
13
|
+
WsResponseCreateEvent,
|
|
14
|
+
WsResponseCreatedEvent,
|
|
15
|
+
WsResponseFailedEvent,
|
|
16
|
+
WsServerEvent,
|
|
17
|
+
} from './types.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { initializeLogger } from '@livekit/agents';
|
|
5
|
+
import { llm, llmStrict } from '@livekit/agents-plugins-test';
|
|
6
|
+
import { describe } from 'vitest';
|
|
7
|
+
import { LLM } from '../responses/llm.js';
|
|
8
|
+
|
|
9
|
+
initializeLogger({ level: 'silent', pretty: false });
|
|
10
|
+
|
|
11
|
+
describe('OpenAI Responses WS wrapper', async () => {
|
|
12
|
+
await llm(
|
|
13
|
+
new LLM({
|
|
14
|
+
temperature: 0,
|
|
15
|
+
strictToolSchema: false,
|
|
16
|
+
useWebSocket: true,
|
|
17
|
+
}),
|
|
18
|
+
true,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('OpenAI Responses WS wrapper strict tool schema', async () => {
|
|
23
|
+
await llmStrict(
|
|
24
|
+
new LLM({
|
|
25
|
+
temperature: 0,
|
|
26
|
+
strictToolSchema: true,
|
|
27
|
+
useWebSocket: true,
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
});
|