@livekit/agents 1.0.47 → 1.0.48
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/beta/index.cjs +29 -0
- package/dist/beta/index.cjs.map +1 -0
- package/dist/beta/index.d.cts +2 -0
- package/dist/beta/index.d.ts +2 -0
- package/dist/beta/index.d.ts.map +1 -0
- package/dist/beta/index.js +7 -0
- package/dist/beta/index.js.map +1 -0
- package/dist/beta/workflows/index.cjs +29 -0
- package/dist/beta/workflows/index.cjs.map +1 -0
- package/dist/beta/workflows/index.d.cts +2 -0
- package/dist/beta/workflows/index.d.ts +2 -0
- package/dist/beta/workflows/index.d.ts.map +1 -0
- package/dist/beta/workflows/index.js +7 -0
- package/dist/beta/workflows/index.js.map +1 -0
- package/dist/beta/workflows/task_group.cjs +162 -0
- package/dist/beta/workflows/task_group.cjs.map +1 -0
- package/dist/beta/workflows/task_group.d.cts +32 -0
- package/dist/beta/workflows/task_group.d.ts +32 -0
- package/dist/beta/workflows/task_group.d.ts.map +1 -0
- package/dist/beta/workflows/task_group.js +138 -0
- package/dist/beta/workflows/task_group.js.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/inference/api_protos.d.cts +59 -59
- package/dist/inference/api_protos.d.ts +59 -59
- package/dist/llm/chat_context.cjs +89 -1
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.cts +10 -1
- package/dist/llm/chat_context.d.ts +10 -1
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +89 -1
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +43 -0
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +43 -0
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +3 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/provider_format/index.d.cts +1 -1
- package/dist/llm/provider_format/index.d.ts +1 -1
- package/dist/llm/tool_context.cjs +7 -0
- package/dist/llm/tool_context.cjs.map +1 -1
- package/dist/llm/tool_context.d.cts +10 -2
- package/dist/llm/tool_context.d.ts +10 -2
- package/dist/llm/tool_context.d.ts.map +1 -1
- package/dist/llm/tool_context.js +6 -0
- package/dist/llm/tool_context.js.map +1 -1
- package/dist/utils.cjs +1 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/agent.cjs +9 -0
- package/dist/voice/agent.cjs.map +1 -1
- package/dist/voice/agent.d.cts +1 -0
- package/dist/voice/agent.d.ts +1 -0
- package/dist/voice/agent.d.ts.map +1 -1
- package/dist/voice/agent.js +9 -0
- package/dist/voice/agent.js.map +1 -1
- package/dist/voice/agent_activity.cjs +31 -8
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.cts +7 -0
- package/dist/voice/agent_activity.d.ts +7 -0
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +31 -8
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/room_io/room_io.cjs +11 -2
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +12 -3
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/dist/voice/testing/fake_llm.cjs +127 -0
- package/dist/voice/testing/fake_llm.cjs.map +1 -0
- package/dist/voice/testing/fake_llm.d.cts +30 -0
- package/dist/voice/testing/fake_llm.d.ts +30 -0
- package/dist/voice/testing/fake_llm.d.ts.map +1 -0
- package/dist/voice/testing/fake_llm.js +103 -0
- package/dist/voice/testing/fake_llm.js.map +1 -0
- package/dist/voice/testing/index.cjs +3 -0
- package/dist/voice/testing/index.cjs.map +1 -1
- package/dist/voice/testing/index.d.cts +1 -0
- package/dist/voice/testing/index.d.ts +1 -0
- package/dist/voice/testing/index.d.ts.map +1 -1
- package/dist/voice/testing/index.js +2 -0
- package/dist/voice/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/src/beta/index.ts +9 -0
- package/src/beta/workflows/index.ts +9 -0
- package/src/beta/workflows/task_group.ts +194 -0
- package/src/index.ts +2 -1
- package/src/llm/chat_context.test.ts +48 -0
- package/src/llm/chat_context.ts +123 -0
- package/src/llm/index.ts +1 -0
- package/src/llm/tool_context.ts +14 -0
- package/src/utils.ts +5 -0
- package/src/voice/agent.ts +11 -0
- package/src/voice/agent_activity.ts +44 -6
- package/src/voice/room_io/room_io.ts +14 -3
- package/src/voice/testing/fake_llm.ts +138 -0
- package/src/voice/testing/index.ts +2 -0
package/src/utils.ts
CHANGED
|
@@ -173,6 +173,11 @@ export class Future<T = void> {
|
|
|
173
173
|
this.#rejected = true;
|
|
174
174
|
this.#error = error;
|
|
175
175
|
this.#rejectPromise(error);
|
|
176
|
+
// Python calls Future.exception() right after set_exception() to silence
|
|
177
|
+
// "exception was never retrieved" warnings. In JS, consume the rejection
|
|
178
|
+
// immediately so Node does not emit unhandled-rejection noise before a
|
|
179
|
+
// later await/catch observes it.
|
|
180
|
+
void this.#await.catch(() => undefined);
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
|
package/src/voice/agent.ts
CHANGED
|
@@ -302,6 +302,17 @@ export class Agent<UserData = any> {
|
|
|
302
302
|
this._agentActivity.updateChatCtx(chatCtx);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
// TODO(parity): Add when AgentConfigUpdate is ported to ChatContext.
|
|
306
|
+
async updateTools(tools: ToolContext): Promise<void> {
|
|
307
|
+
if (!this._agentActivity) {
|
|
308
|
+
this._tools = { ...tools };
|
|
309
|
+
this._chatCtx = this._chatCtx.copy({ toolCtx: this._tools });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await this._agentActivity.updateTools(tools);
|
|
314
|
+
}
|
|
315
|
+
|
|
305
316
|
static default = {
|
|
306
317
|
async sttNode(
|
|
307
318
|
agent: Agent,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type RealtimeSession,
|
|
24
24
|
type ToolChoice,
|
|
25
25
|
type ToolContext,
|
|
26
|
+
ToolFlag,
|
|
26
27
|
} from '../llm/index.js';
|
|
27
28
|
import type { LLMError } from '../llm/llm.js';
|
|
28
29
|
import { isSameToolChoice, isSameToolContext } from '../llm/tool_context.js';
|
|
@@ -83,6 +84,12 @@ import { SpeechHandle } from './speech_handle.js';
|
|
|
83
84
|
import { setParticipantSpanAttributes } from './utils.js';
|
|
84
85
|
|
|
85
86
|
export const agentActivityStorage = new AsyncLocalStorage<AgentActivity>();
|
|
87
|
+
export const onEnterStorage = new AsyncLocalStorage<OnEnterData>();
|
|
88
|
+
|
|
89
|
+
interface OnEnterData {
|
|
90
|
+
session: AgentSession;
|
|
91
|
+
agent: Agent;
|
|
92
|
+
}
|
|
86
93
|
|
|
87
94
|
interface PreemptiveGeneration {
|
|
88
95
|
speechHandle: SpeechHandle;
|
|
@@ -312,6 +319,8 @@ export class AgentActivity implements RecognitionHooks {
|
|
|
312
319
|
}
|
|
313
320
|
}
|
|
314
321
|
|
|
322
|
+
// TODO(parity): Record initial AgentConfigUpdate in chat context
|
|
323
|
+
|
|
315
324
|
// metrics and error handling
|
|
316
325
|
if (this.llm instanceof LLM) {
|
|
317
326
|
this.llm.on('metrics_collected', this.onMetricsCollected);
|
|
@@ -354,11 +363,13 @@ export class AgentActivity implements RecognitionHooks {
|
|
|
354
363
|
if (runOnEnter) {
|
|
355
364
|
this._onEnterTask = this.createSpeechTask({
|
|
356
365
|
taskFn: () =>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
366
|
+
onEnterStorage.run({ session: this.agentSession, agent: this.agent }, () =>
|
|
367
|
+
tracer.startActiveSpan(async () => this.agent.onEnter(), {
|
|
368
|
+
name: 'on_enter',
|
|
369
|
+
context: trace.setSpan(ROOT_CONTEXT, startSpan),
|
|
370
|
+
attributes: { [traceTypes.ATTR_AGENT_LABEL]: this.agent.id },
|
|
371
|
+
}),
|
|
372
|
+
),
|
|
362
373
|
inlineTask: true,
|
|
363
374
|
name: 'AgentActivity_onEnter',
|
|
364
375
|
});
|
|
@@ -446,6 +457,20 @@ export class AgentActivity implements RecognitionHooks {
|
|
|
446
457
|
}
|
|
447
458
|
}
|
|
448
459
|
|
|
460
|
+
// TODO: Add when AgentConfigUpdate is ported to ChatContext.
|
|
461
|
+
async updateTools(tools: ToolContext): Promise<void> {
|
|
462
|
+
this.agent._tools = { ...tools };
|
|
463
|
+
|
|
464
|
+
if (this.realtimeSession) {
|
|
465
|
+
await this.realtimeSession.updateTools(tools);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (this.llm instanceof LLM) {
|
|
469
|
+
// for realtime LLM, we assume the server will remove unvalid tool messages
|
|
470
|
+
await this.updateChatCtx(this.agent._chatCtx.copy({ toolCtx: tools }));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
449
474
|
updateOptions({ toolChoice }: { toolChoice?: ToolChoice | null }): void {
|
|
450
475
|
if (toolChoice !== undefined) {
|
|
451
476
|
this.toolChoice = toolChoice;
|
|
@@ -1129,12 +1154,25 @@ export class AgentActivity implements RecognitionHooks {
|
|
|
1129
1154
|
instructions = `${this.agent.instructions}\n${instructions}`;
|
|
1130
1155
|
}
|
|
1131
1156
|
|
|
1157
|
+
// Filter out tools with IGNORE_ON_ENTER flag when generateReply is called inside onEnter
|
|
1158
|
+
const onEnterData = onEnterStorage.getStore();
|
|
1159
|
+
const shouldFilterTools =
|
|
1160
|
+
onEnterData?.agent === this.agent && onEnterData?.session === this.agentSession;
|
|
1161
|
+
|
|
1162
|
+
const tools = shouldFilterTools
|
|
1163
|
+
? Object.fromEntries(
|
|
1164
|
+
Object.entries(this.agent.toolCtx).filter(
|
|
1165
|
+
([, fnTool]) => !(fnTool.flags & ToolFlag.IGNORE_ON_ENTER),
|
|
1166
|
+
),
|
|
1167
|
+
)
|
|
1168
|
+
: this.agent.toolCtx;
|
|
1169
|
+
|
|
1132
1170
|
const task = this.createSpeechTask({
|
|
1133
1171
|
taskFn: (abortController: AbortController) =>
|
|
1134
1172
|
this.pipelineReplyTask(
|
|
1135
1173
|
handle,
|
|
1136
1174
|
chatCtx ?? this.agent.chatCtx,
|
|
1137
|
-
|
|
1175
|
+
tools,
|
|
1138
1176
|
{
|
|
1139
1177
|
toolChoice: toOaiToolChoice(toolChoice !== undefined ? toolChoice : this.toolChoice),
|
|
1140
1178
|
},
|
|
@@ -21,7 +21,7 @@ import type { WritableStreamDefaultWriter } from 'node:stream/web';
|
|
|
21
21
|
import { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';
|
|
22
22
|
import { log } from '../../log.js';
|
|
23
23
|
import { IdentityTransform } from '../../stream/identity_transform.js';
|
|
24
|
-
import { Future, Task } from '../../utils.js';
|
|
24
|
+
import { Future, Task, waitForAbort } from '../../utils.js';
|
|
25
25
|
import { type AgentSession } from '../agent_session.js';
|
|
26
26
|
import {
|
|
27
27
|
AgentSessionEventTypes,
|
|
@@ -177,7 +177,10 @@ export class RoomIO {
|
|
|
177
177
|
: this.inputOptions.participantIdentity ?? null;
|
|
178
178
|
}
|
|
179
179
|
private async init(signal: AbortSignal): Promise<void> {
|
|
180
|
-
await this.roomConnectedFuture.await;
|
|
180
|
+
await Promise.race([this.roomConnectedFuture.await, waitForAbort(signal)]);
|
|
181
|
+
if (signal.aborted) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
181
184
|
|
|
182
185
|
for (const participant of this.room.remoteParticipants.values()) {
|
|
183
186
|
this.onParticipantConnected(participant);
|
|
@@ -186,7 +189,15 @@ export class RoomIO {
|
|
|
186
189
|
return;
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
const participant = await
|
|
192
|
+
const participant = await Promise.race([
|
|
193
|
+
this.participantAvailableFuture.await,
|
|
194
|
+
waitForAbort(signal),
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
if (!participant) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
190
201
|
this.setParticipant(participant.identity);
|
|
191
202
|
|
|
192
203
|
// init agent outputs
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import type { ChatContext } from '../../llm/chat_context.js';
|
|
5
|
+
import { FunctionCall } from '../../llm/chat_context.js';
|
|
6
|
+
import { LLMStream as BaseLLMStream, LLM, type LLMStream } from '../../llm/llm.js';
|
|
7
|
+
import type { ToolChoice, ToolContext } from '../../llm/tool_context.js';
|
|
8
|
+
import { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../../types.js';
|
|
9
|
+
import { delay } from '../../utils.js';
|
|
10
|
+
|
|
11
|
+
export interface FakeLLMResponse {
|
|
12
|
+
input: string;
|
|
13
|
+
type?: 'llm';
|
|
14
|
+
content?: string;
|
|
15
|
+
ttft?: number;
|
|
16
|
+
duration?: number;
|
|
17
|
+
toolCalls?: Array<{ name: string; args: Record<string, unknown> }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class FakeLLM extends LLM {
|
|
21
|
+
private readonly responseMap = new Map<string, FakeLLMResponse>();
|
|
22
|
+
|
|
23
|
+
constructor(responses: FakeLLMResponse[] = []) {
|
|
24
|
+
super();
|
|
25
|
+
for (const response of responses) {
|
|
26
|
+
this.responseMap.set(response.input, {
|
|
27
|
+
type: 'llm',
|
|
28
|
+
ttft: 0,
|
|
29
|
+
duration: 0,
|
|
30
|
+
...response,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
label(): string {
|
|
36
|
+
return 'fake-llm';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
chat({
|
|
40
|
+
chatCtx,
|
|
41
|
+
toolCtx,
|
|
42
|
+
connOptions = DEFAULT_API_CONNECT_OPTIONS,
|
|
43
|
+
}: {
|
|
44
|
+
chatCtx: ChatContext;
|
|
45
|
+
toolCtx?: ToolContext;
|
|
46
|
+
connOptions?: APIConnectOptions;
|
|
47
|
+
parallelToolCalls?: boolean;
|
|
48
|
+
toolChoice?: ToolChoice;
|
|
49
|
+
extraKwargs?: Record<string, unknown>;
|
|
50
|
+
}): LLMStream {
|
|
51
|
+
return new FakeLLMStream(this, {
|
|
52
|
+
chatCtx,
|
|
53
|
+
toolCtx,
|
|
54
|
+
connOptions,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lookup(input: string): FakeLLMResponse | undefined {
|
|
59
|
+
return this.responseMap.get(input);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class FakeLLMStream extends BaseLLMStream {
|
|
64
|
+
private readonly fake: FakeLLM;
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
fake: FakeLLM,
|
|
68
|
+
params: { chatCtx: ChatContext; toolCtx?: ToolContext; connOptions: APIConnectOptions },
|
|
69
|
+
) {
|
|
70
|
+
super(fake, params);
|
|
71
|
+
this.fake = fake;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected async run(): Promise<void> {
|
|
75
|
+
const input = this.getInputText();
|
|
76
|
+
const decision = this.fake.lookup(input);
|
|
77
|
+
if (!decision) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const startedAt = Date.now();
|
|
82
|
+
if ((decision.ttft ?? 0) > 0) {
|
|
83
|
+
await delay(decision.ttft!);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const content = decision.content ?? '';
|
|
87
|
+
const chunkSize = 3;
|
|
88
|
+
for (let i = 0; i < content.length; i += chunkSize) {
|
|
89
|
+
this.queue.put({
|
|
90
|
+
id: 'fake',
|
|
91
|
+
delta: { role: 'assistant', content: content.slice(i, i + chunkSize) },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (decision.toolCalls && decision.toolCalls.length > 0) {
|
|
96
|
+
const calls = decision.toolCalls.map((tc, index) =>
|
|
97
|
+
FunctionCall.create({
|
|
98
|
+
callId: `fake_call_${index}`,
|
|
99
|
+
name: tc.name,
|
|
100
|
+
args: JSON.stringify(tc.args),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
this.queue.put({
|
|
104
|
+
id: 'fake',
|
|
105
|
+
delta: { role: 'assistant', toolCalls: calls },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const elapsed = Date.now() - startedAt;
|
|
110
|
+
const waitMs = Math.max(0, (decision.duration ?? 0) - elapsed);
|
|
111
|
+
if (waitMs > 0) {
|
|
112
|
+
await delay(waitMs);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private getInputText(): string {
|
|
117
|
+
const items = this.chatCtx.items;
|
|
118
|
+
if (items.length === 0) {
|
|
119
|
+
throw new Error('No input text found');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const item of items) {
|
|
123
|
+
if (item.type === 'message' && item.role === 'system') {
|
|
124
|
+
const text = item.textContent ?? '';
|
|
125
|
+
const lines = text.split('\n');
|
|
126
|
+
const tail = lines[lines.length - 1] ?? '';
|
|
127
|
+
if (lines.length > 1 && tail.startsWith('instructions:')) {
|
|
128
|
+
return tail;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const last = items[items.length - 1]!;
|
|
134
|
+
if (last.type === 'message' && last.role === 'user') return last.textContent ?? '';
|
|
135
|
+
if (last.type === 'function_call_output') return last.output;
|
|
136
|
+
throw new Error('No input text found');
|
|
137
|
+
}
|
|
138
|
+
}
|