@livekit/agents 1.0.17 → 1.0.19
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 +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 +12 -12
- package/dist/inference/api_protos.d.ts +12 -12
- package/dist/inference/llm.cjs +35 -13
- package/dist/inference/llm.cjs.map +1 -1
- package/dist/inference/llm.d.cts +10 -5
- package/dist/inference/llm.d.ts +10 -5
- package/dist/inference/llm.d.ts.map +1 -1
- package/dist/inference/llm.js +35 -13
- package/dist/inference/llm.js.map +1 -1
- package/dist/inference/tts.cjs +1 -1
- package/dist/inference/tts.cjs.map +1 -1
- package/dist/inference/tts.js +1 -1
- package/dist/inference/tts.js.map +1 -1
- package/dist/ipc/job_proc_lazy_main.cjs +6 -2
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +6 -2
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/job.cjs +31 -0
- package/dist/job.cjs.map +1 -1
- package/dist/job.d.cts +6 -0
- package/dist/job.d.ts +6 -0
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +31 -0
- package/dist/job.js.map +1 -1
- package/dist/llm/chat_context.cjs +33 -0
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.cts +22 -2
- package/dist/llm/chat_context.d.ts +22 -2
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +32 -0
- package/dist/llm/chat_context.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 +2 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/llm.cjs.map +1 -1
- package/dist/llm/llm.d.cts +1 -1
- package/dist/llm/llm.d.ts +1 -1
- package/dist/llm/llm.d.ts.map +1 -1
- package/dist/llm/llm.js.map +1 -1
- package/dist/llm/provider_format/google.cjs.map +1 -1
- package/dist/llm/provider_format/google.d.cts +1 -1
- package/dist/llm/provider_format/google.d.ts +1 -1
- package/dist/llm/provider_format/google.d.ts.map +1 -1
- package/dist/llm/provider_format/google.js.map +1 -1
- package/dist/llm/provider_format/google.test.cjs +48 -0
- package/dist/llm/provider_format/google.test.cjs.map +1 -1
- package/dist/llm/provider_format/google.test.js +54 -1
- package/dist/llm/provider_format/google.test.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/provider_format/index.d.ts.map +1 -1
- package/dist/llm/provider_format/openai.cjs +1 -2
- package/dist/llm/provider_format/openai.cjs.map +1 -1
- package/dist/llm/provider_format/openai.js +1 -2
- package/dist/llm/provider_format/openai.js.map +1 -1
- package/dist/llm/provider_format/openai.test.cjs +32 -0
- package/dist/llm/provider_format/openai.test.cjs.map +1 -1
- package/dist/llm/provider_format/openai.test.js +38 -1
- package/dist/llm/provider_format/openai.test.js.map +1 -1
- package/dist/llm/realtime.cjs.map +1 -1
- package/dist/llm/realtime.d.cts +4 -0
- package/dist/llm/realtime.d.ts +4 -0
- package/dist/llm/realtime.d.ts.map +1 -1
- package/dist/llm/realtime.js.map +1 -1
- package/dist/llm/utils.cjs +2 -2
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +1 -1
- package/dist/llm/utils.d.ts +1 -1
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +2 -2
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/zod-utils.cjs +6 -3
- package/dist/llm/zod-utils.cjs.map +1 -1
- package/dist/llm/zod-utils.d.cts +1 -1
- package/dist/llm/zod-utils.d.ts +1 -1
- package/dist/llm/zod-utils.d.ts.map +1 -1
- package/dist/llm/zod-utils.js +6 -3
- package/dist/llm/zod-utils.js.map +1 -1
- package/dist/llm/zod-utils.test.cjs +83 -0
- package/dist/llm/zod-utils.test.cjs.map +1 -1
- package/dist/llm/zod-utils.test.js +83 -0
- package/dist/llm/zod-utils.test.js.map +1 -1
- package/dist/log.cjs.map +1 -1
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js.map +1 -1
- package/dist/telemetry/index.cjs +51 -0
- package/dist/telemetry/index.cjs.map +1 -0
- package/dist/telemetry/index.d.cts +4 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +12 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/trace_types.cjs +191 -0
- package/dist/telemetry/trace_types.cjs.map +1 -0
- package/dist/telemetry/trace_types.d.cts +56 -0
- package/dist/telemetry/trace_types.d.ts +56 -0
- package/dist/telemetry/trace_types.d.ts.map +1 -0
- package/dist/telemetry/trace_types.js +113 -0
- package/dist/telemetry/trace_types.js.map +1 -0
- package/dist/telemetry/traces.cjs +196 -0
- package/dist/telemetry/traces.cjs.map +1 -0
- package/dist/telemetry/traces.d.cts +97 -0
- package/dist/telemetry/traces.d.ts +97 -0
- package/dist/telemetry/traces.d.ts.map +1 -0
- package/dist/telemetry/traces.js +173 -0
- package/dist/telemetry/traces.js.map +1 -0
- package/dist/telemetry/utils.cjs +86 -0
- package/dist/telemetry/utils.cjs.map +1 -0
- package/dist/telemetry/utils.d.cts +5 -0
- package/dist/telemetry/utils.d.ts +5 -0
- package/dist/telemetry/utils.d.ts.map +1 -0
- package/dist/telemetry/utils.js +51 -0
- package/dist/telemetry/utils.js.map +1 -0
- package/dist/tts/tts.cjs.map +1 -1
- package/dist/tts/tts.d.ts.map +1 -1
- package/dist/tts/tts.js.map +1 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +7 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/voice/agent.cjs +15 -0
- package/dist/voice/agent.cjs.map +1 -1
- package/dist/voice/agent.d.cts +4 -1
- package/dist/voice/agent.d.ts +4 -1
- package/dist/voice/agent.d.ts.map +1 -1
- package/dist/voice/agent.js +15 -0
- package/dist/voice/agent.js.map +1 -1
- package/dist/voice/agent_activity.cjs +71 -20
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +71 -20
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +69 -2
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +11 -2
- package/dist/voice/agent_session.d.ts +11 -2
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +70 -3
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/audio_recognition.cjs.map +1 -1
- package/dist/voice/audio_recognition.d.ts.map +1 -1
- package/dist/voice/audio_recognition.js.map +1 -1
- package/dist/voice/generation.cjs.map +1 -1
- package/dist/voice/generation.d.ts.map +1 -1
- package/dist/voice/generation.js.map +1 -1
- package/dist/voice/index.cjs +2 -0
- package/dist/voice/index.cjs.map +1 -1
- package/dist/voice/index.d.cts +1 -0
- package/dist/voice/index.d.ts +1 -0
- package/dist/voice/index.d.ts.map +1 -1
- package/dist/voice/index.js +1 -0
- package/dist/voice/index.js.map +1 -1
- package/dist/voice/interruption_detection.test.cjs +114 -0
- package/dist/voice/interruption_detection.test.cjs.map +1 -0
- package/dist/voice/interruption_detection.test.js +113 -0
- package/dist/voice/interruption_detection.test.js.map +1 -0
- package/dist/voice/report.cjs +69 -0
- package/dist/voice/report.cjs.map +1 -0
- package/dist/voice/report.d.cts +26 -0
- package/dist/voice/report.d.ts +26 -0
- package/dist/voice/report.d.ts.map +1 -0
- package/dist/voice/report.js +44 -0
- package/dist/voice/report.js.map +1 -0
- package/dist/voice/room_io/room_io.cjs +3 -0
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.cts +1 -0
- package/dist/voice/room_io/room_io.d.ts +1 -0
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +3 -0
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/package.json +12 -5
- package/src/index.ts +2 -1
- package/src/inference/llm.ts +53 -21
- package/src/inference/tts.ts +1 -1
- package/src/ipc/job_proc_lazy_main.ts +10 -2
- package/src/job.ts +48 -0
- package/src/llm/__snapshots__/zod-utils.test.ts.snap +218 -0
- package/src/llm/chat_context.ts +53 -1
- package/src/llm/index.ts +1 -0
- package/src/llm/llm.ts +3 -1
- package/src/llm/provider_format/google.test.ts +72 -1
- package/src/llm/provider_format/google.ts +4 -4
- package/src/llm/provider_format/openai.test.ts +55 -1
- package/src/llm/provider_format/openai.ts +3 -2
- package/src/llm/realtime.ts +8 -1
- package/src/llm/utils.ts +7 -2
- package/src/llm/zod-utils.test.ts +101 -0
- package/src/llm/zod-utils.ts +12 -3
- package/src/log.ts +1 -0
- package/src/telemetry/index.ts +10 -0
- package/src/telemetry/trace_types.ts +88 -0
- package/src/telemetry/traces.ts +266 -0
- package/src/telemetry/utils.ts +61 -0
- package/src/tts/tts.ts +4 -0
- package/src/utils.ts +17 -0
- package/src/voice/agent.ts +22 -0
- package/src/voice/agent_activity.ts +102 -24
- package/src/voice/agent_session.ts +98 -1
- package/src/voice/audio_recognition.ts +2 -0
- package/src/voice/generation.ts +3 -0
- package/src/voice/index.ts +1 -0
- package/src/voice/interruption_detection.test.ts +151 -0
- package/src/voice/report.ts +77 -0
- package/src/voice/room_io/room_io.ts +4 -0
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
import { VideoBufferType, VideoFrame } from '@livekit/rtc-node';
|
|
5
5
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import { initializeLogger } from '../../log.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AgentHandoffItem,
|
|
9
|
+
ChatContext,
|
|
10
|
+
FunctionCall,
|
|
11
|
+
FunctionCallOutput,
|
|
12
|
+
} from '../chat_context.js';
|
|
8
13
|
import { serializeImage } from '../utils.js';
|
|
9
14
|
import { toChatCtx } from './google.js';
|
|
10
15
|
|
|
@@ -769,4 +774,70 @@ describe('Google Provider Format - toChatCtx', () => {
|
|
|
769
774
|
]);
|
|
770
775
|
expect(formatData.systemMessages).toBeNull();
|
|
771
776
|
});
|
|
777
|
+
|
|
778
|
+
it('should filter out agent handoff items', async () => {
|
|
779
|
+
const ctx = ChatContext.empty();
|
|
780
|
+
|
|
781
|
+
ctx.addMessage({ role: 'user', content: 'Hello' });
|
|
782
|
+
|
|
783
|
+
// Insert an agent handoff item
|
|
784
|
+
const handoff = new AgentHandoffItem({
|
|
785
|
+
oldAgentId: 'agent_1',
|
|
786
|
+
newAgentId: 'agent_2',
|
|
787
|
+
});
|
|
788
|
+
ctx.insert(handoff);
|
|
789
|
+
|
|
790
|
+
ctx.addMessage({ role: 'assistant', content: 'Hi there!' });
|
|
791
|
+
|
|
792
|
+
const [result, formatData] = await toChatCtx(ctx, false);
|
|
793
|
+
|
|
794
|
+
// Agent handoff should be filtered out, only messages should remain
|
|
795
|
+
expect(result).toEqual([
|
|
796
|
+
{
|
|
797
|
+
role: 'user',
|
|
798
|
+
parts: [{ text: 'Hello' }],
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
role: 'model',
|
|
802
|
+
parts: [{ text: 'Hi there!' }],
|
|
803
|
+
},
|
|
804
|
+
]);
|
|
805
|
+
expect(formatData.systemMessages).toBeNull();
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it('should handle multiple agent handoffs without errors', async () => {
|
|
809
|
+
const ctx = ChatContext.empty();
|
|
810
|
+
|
|
811
|
+
ctx.addMessage({ role: 'user', content: 'Start' });
|
|
812
|
+
|
|
813
|
+
// Multiple handoffs
|
|
814
|
+
ctx.insert(new AgentHandoffItem({ oldAgentId: undefined, newAgentId: 'agent_1' }));
|
|
815
|
+
ctx.addMessage({ role: 'assistant', content: 'Response from agent 1' });
|
|
816
|
+
|
|
817
|
+
ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_1', newAgentId: 'agent_2' }));
|
|
818
|
+
ctx.addMessage({ role: 'assistant', content: 'Response from agent 2' });
|
|
819
|
+
|
|
820
|
+
ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_2', newAgentId: 'agent_3' }));
|
|
821
|
+
ctx.addMessage({ role: 'assistant', content: 'Response from agent 3' });
|
|
822
|
+
|
|
823
|
+
const [result, formatData] = await toChatCtx(ctx, false);
|
|
824
|
+
|
|
825
|
+
// All handoffs should be filtered out
|
|
826
|
+
// Note: Google provider groups consecutive messages by the same role
|
|
827
|
+
expect(result).toEqual([
|
|
828
|
+
{
|
|
829
|
+
role: 'user',
|
|
830
|
+
parts: [{ text: 'Start' }],
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
role: 'model',
|
|
834
|
+
parts: [
|
|
835
|
+
{ text: 'Response from agent 1' },
|
|
836
|
+
{ text: 'Response from agent 2' },
|
|
837
|
+
{ text: 'Response from agent 3' },
|
|
838
|
+
],
|
|
839
|
+
},
|
|
840
|
+
]);
|
|
841
|
+
expect(formatData.systemMessages).toBeNull();
|
|
842
|
+
});
|
|
772
843
|
});
|
|
@@ -12,11 +12,11 @@ export interface GoogleFormatData {
|
|
|
12
12
|
export async function toChatCtx(
|
|
13
13
|
chatCtx: ChatContext,
|
|
14
14
|
injectDummyUserMessage: boolean = true,
|
|
15
|
-
): Promise<[Record<string,
|
|
16
|
-
const turns: Record<string,
|
|
15
|
+
): Promise<[Record<string, unknown>[], GoogleFormatData]> {
|
|
16
|
+
const turns: Record<string, unknown>[] = [];
|
|
17
17
|
const systemMessages: string[] = [];
|
|
18
18
|
let currentRole: string | null = null;
|
|
19
|
-
let parts: Record<string,
|
|
19
|
+
let parts: Record<string, unknown>[] = [];
|
|
20
20
|
|
|
21
21
|
// Flatten all grouped tool calls to get individual messages
|
|
22
22
|
const itemGroups = groupToolCalls(chatCtx);
|
|
@@ -104,7 +104,7 @@ export async function toChatCtx(
|
|
|
104
104
|
];
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
async function toImagePart(image: ImageContent): Promise<Record<string,
|
|
107
|
+
async function toImagePart(image: ImageContent): Promise<Record<string, unknown>> {
|
|
108
108
|
const cacheKey = 'serialized_image';
|
|
109
109
|
if (!image._cache[cacheKey]) {
|
|
110
110
|
image._cache[cacheKey] = await serializeImage(image);
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
import { VideoBufferType, VideoFrame } from '@livekit/rtc-node';
|
|
5
5
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import { initializeLogger } from '../../log.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AgentHandoffItem,
|
|
9
|
+
ChatContext,
|
|
10
|
+
FunctionCall,
|
|
11
|
+
FunctionCallOutput,
|
|
12
|
+
} from '../chat_context.js';
|
|
8
13
|
import { serializeImage } from '../utils.js';
|
|
9
14
|
import { toChatCtx } from './openai.js';
|
|
10
15
|
|
|
@@ -578,4 +583,53 @@ describe('toChatCtx', () => {
|
|
|
578
583
|
},
|
|
579
584
|
]);
|
|
580
585
|
});
|
|
586
|
+
|
|
587
|
+
it('should filter out agent handoff items', async () => {
|
|
588
|
+
const ctx = ChatContext.empty();
|
|
589
|
+
|
|
590
|
+
ctx.addMessage({ role: 'user', content: 'Hello' });
|
|
591
|
+
|
|
592
|
+
// Insert an agent handoff item
|
|
593
|
+
const handoff = new AgentHandoffItem({
|
|
594
|
+
oldAgentId: 'agent_1',
|
|
595
|
+
newAgentId: 'agent_2',
|
|
596
|
+
});
|
|
597
|
+
ctx.insert(handoff);
|
|
598
|
+
|
|
599
|
+
ctx.addMessage({ role: 'assistant', content: 'Hi there!' });
|
|
600
|
+
|
|
601
|
+
const result = await toChatCtx(ctx);
|
|
602
|
+
|
|
603
|
+
// Agent handoff should be filtered out, only messages should remain
|
|
604
|
+
expect(result).toEqual([
|
|
605
|
+
{ role: 'user', content: 'Hello' },
|
|
606
|
+
{ role: 'assistant', content: 'Hi there!' },
|
|
607
|
+
]);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('should handle multiple agent handoffs without errors', async () => {
|
|
611
|
+
const ctx = ChatContext.empty();
|
|
612
|
+
|
|
613
|
+
ctx.addMessage({ role: 'user', content: 'Start' });
|
|
614
|
+
|
|
615
|
+
// Multiple handoffs
|
|
616
|
+
ctx.insert(new AgentHandoffItem({ oldAgentId: undefined, newAgentId: 'agent_1' }));
|
|
617
|
+
ctx.addMessage({ role: 'assistant', content: 'Response from agent 1' });
|
|
618
|
+
|
|
619
|
+
ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_1', newAgentId: 'agent_2' }));
|
|
620
|
+
ctx.addMessage({ role: 'assistant', content: 'Response from agent 2' });
|
|
621
|
+
|
|
622
|
+
ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_2', newAgentId: 'agent_3' }));
|
|
623
|
+
ctx.addMessage({ role: 'assistant', content: 'Response from agent 3' });
|
|
624
|
+
|
|
625
|
+
const result = await toChatCtx(ctx);
|
|
626
|
+
|
|
627
|
+
// All handoffs should be filtered out
|
|
628
|
+
expect(result).toEqual([
|
|
629
|
+
{ role: 'user', content: 'Start' },
|
|
630
|
+
{ role: 'assistant', content: 'Response from agent 1' },
|
|
631
|
+
{ role: 'assistant', content: 'Response from agent 2' },
|
|
632
|
+
{ role: 'assistant', content: 'Response from agent 3' },
|
|
633
|
+
]);
|
|
634
|
+
});
|
|
581
635
|
});
|
|
@@ -78,9 +78,10 @@ async function toChatItem(item: ChatItem) {
|
|
|
78
78
|
tool_call_id: item.callId,
|
|
79
79
|
content: item.output,
|
|
80
80
|
};
|
|
81
|
-
} else {
|
|
82
|
-
throw new Error(`Unsupported item type: ${item['type']}`);
|
|
83
81
|
}
|
|
82
|
+
// Skip other item types (e.g., agent_handoff)
|
|
83
|
+
// These should be filtered by groupToolCalls, but this is a safety net
|
|
84
|
+
throw new Error(`Unsupported item type: ${item['type']}`);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
async function toImageContent(content: ImageContent) {
|
package/src/llm/realtime.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface MessageGeneration {
|
|
|
19
19
|
messageId: string;
|
|
20
20
|
textStream: ReadableStream<string>;
|
|
21
21
|
audioStream: ReadableStream<AudioFrame>;
|
|
22
|
+
modalities?: Promise<('text' | 'audio')[]>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface GenerationCreatedEvent {
|
|
@@ -40,6 +41,7 @@ export interface RealtimeCapabilities {
|
|
|
40
41
|
turnDetection: boolean;
|
|
41
42
|
userTranscription: boolean;
|
|
42
43
|
autoToolReplyGeneration: boolean;
|
|
44
|
+
audioOutput: boolean;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export interface InputTranscriptionCompleted {
|
|
@@ -121,7 +123,12 @@ export abstract class RealtimeSession extends EventEmitter {
|
|
|
121
123
|
/**
|
|
122
124
|
* Truncate the message at the given audio end time
|
|
123
125
|
*/
|
|
124
|
-
abstract truncate(options: {
|
|
126
|
+
abstract truncate(options: {
|
|
127
|
+
messageId: string;
|
|
128
|
+
audioEndMs: number;
|
|
129
|
+
modalities?: ('text' | 'audio')[];
|
|
130
|
+
audioTranscript?: string;
|
|
131
|
+
}): Promise<void>;
|
|
125
132
|
|
|
126
133
|
async close(): Promise<void> {
|
|
127
134
|
this._mainTask.cancel();
|
package/src/llm/utils.ts
CHANGED
|
@@ -323,9 +323,14 @@ export function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): Di
|
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
export function toJsonSchema(
|
|
326
|
+
export function toJsonSchema(
|
|
327
|
+
schema: ToolInputSchema<any>,
|
|
328
|
+
isOpenai: boolean = true,
|
|
329
|
+
strict: boolean = false,
|
|
330
|
+
): JSONSchema7 {
|
|
327
331
|
if (isZodSchema(schema)) {
|
|
328
|
-
return zodSchemaToJsonSchema(schema, isOpenai);
|
|
332
|
+
return zodSchemaToJsonSchema(schema, isOpenai, strict);
|
|
329
333
|
}
|
|
334
|
+
|
|
330
335
|
return schema as JSONSchema7;
|
|
331
336
|
}
|
|
@@ -260,6 +260,107 @@ describe('Zod Utils', () => {
|
|
|
260
260
|
expect(jsonSchema7).toHaveProperty('properties');
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
|
+
|
|
264
|
+
describe('strict parameter', () => {
|
|
265
|
+
it('should produce strict JSON schema with strict: true', () => {
|
|
266
|
+
const schema = z4.object({
|
|
267
|
+
name: z4.string(),
|
|
268
|
+
age: z4.number(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const strictSchema = zodSchemaToJsonSchema(schema, true, true);
|
|
272
|
+
expect(strictSchema).toMatchSnapshot();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle nullable fields in strict mode', () => {
|
|
276
|
+
const schema = z4.object({
|
|
277
|
+
required: z4.string(),
|
|
278
|
+
optional: z4.string().nullable(),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const strictSchema = zodSchemaToJsonSchema(schema, true, true);
|
|
282
|
+
expect(strictSchema).toMatchSnapshot();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should handle default values in strict mode', () => {
|
|
286
|
+
const schema = z4.object({
|
|
287
|
+
name: z4.string(),
|
|
288
|
+
role: z4.string().default('user'),
|
|
289
|
+
active: z4.boolean().default(true),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const strictSchema = zodSchemaToJsonSchema(schema, true, true);
|
|
293
|
+
expect(strictSchema).toMatchSnapshot();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should handle nested objects in strict mode', () => {
|
|
297
|
+
const schema = z4.object({
|
|
298
|
+
user: z4.object({
|
|
299
|
+
name: z4.string(),
|
|
300
|
+
email: z4.string().nullable(),
|
|
301
|
+
}),
|
|
302
|
+
metadata: z4.object({
|
|
303
|
+
created: z4.string(),
|
|
304
|
+
}),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const strictSchema = zodSchemaToJsonSchema(schema, true, true);
|
|
308
|
+
expect(strictSchema).toMatchSnapshot();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should handle arrays in strict mode', () => {
|
|
312
|
+
const schema = z4.object({
|
|
313
|
+
tags: z4.array(z4.string()),
|
|
314
|
+
numbers: z4.array(z4.number()),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const strictSchema = zodSchemaToJsonSchema(schema, true, true);
|
|
318
|
+
expect(strictSchema).toMatchSnapshot();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should handle v3 schemas in strict mode', () => {
|
|
322
|
+
const schema = z3.object({
|
|
323
|
+
name: z3.string(),
|
|
324
|
+
age: z3.number().optional(),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const strictSchema = zodSchemaToJsonSchema(schema, true, true);
|
|
328
|
+
expect(strictSchema).toMatchSnapshot();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should throw error when using .optional() without .nullable() in strict mode', () => {
|
|
332
|
+
const schema = z4.object({
|
|
333
|
+
required: z4.string(),
|
|
334
|
+
optional: z4.string().optional(),
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
expect(() => zodSchemaToJsonSchema(schema, true, true)).toThrow(
|
|
338
|
+
/uses `.optional\(\)` without `.nullable\(\)` which is not supported by the API/,
|
|
339
|
+
);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should throw error for nested .optional() fields in strict mode', () => {
|
|
343
|
+
const schema = z4.object({
|
|
344
|
+
user: z4.object({
|
|
345
|
+
name: z4.string(),
|
|
346
|
+
email: z4.string().optional(),
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(() => zodSchemaToJsonSchema(schema, true, true)).toThrow(
|
|
351
|
+
/uses `.optional\(\)` without `.nullable\(\)` which is not supported by the API/,
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should NOT throw error when using .optional() in non-strict mode', () => {
|
|
356
|
+
const schema = z4.object({
|
|
357
|
+
required: z4.string(),
|
|
358
|
+
optional: z4.string().optional(),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(() => zodSchemaToJsonSchema(schema, true, false)).not.toThrow();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
263
364
|
});
|
|
264
365
|
|
|
265
366
|
describe('parseZodSchema', () => {
|
package/src/llm/zod-utils.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import type { JSONSchema7 } from 'json-schema';
|
|
5
|
+
import { toStrictJsonSchema } from 'openai/lib/transform';
|
|
5
6
|
import { zodToJsonSchema as zodToJsonSchemaV3 } from 'zod-to-json-schema';
|
|
6
7
|
import type * as z3 from 'zod/v3';
|
|
7
8
|
import * as z4 from 'zod/v4';
|
|
@@ -101,12 +102,18 @@ export function isZodObjectSchema(schema: ZodSchema): boolean {
|
|
|
101
102
|
* @param isOpenai - Whether to use OpenAI-specific formatting (default: true)
|
|
102
103
|
* @returns A JSON Schema representation of the Zod schema
|
|
103
104
|
*/
|
|
104
|
-
export function zodSchemaToJsonSchema(
|
|
105
|
+
export function zodSchemaToJsonSchema(
|
|
106
|
+
schema: ZodSchema,
|
|
107
|
+
isOpenai: boolean = true,
|
|
108
|
+
strict: boolean = false,
|
|
109
|
+
): JSONSchema7 {
|
|
110
|
+
let result: JSONSchema7;
|
|
111
|
+
|
|
105
112
|
if (isZod4Schema(schema)) {
|
|
106
113
|
// Zod v4 has native toJSONSchema support
|
|
107
114
|
// Configuration adapted from Vercel AI SDK to support OpenAPI conversion for Google
|
|
108
115
|
// Source: https://github.com/vercel/ai/blob/main/packages/provider-utils/src/schema.ts#L255-L258
|
|
109
|
-
|
|
116
|
+
result = z4.toJSONSchema(schema, {
|
|
110
117
|
target: 'draft-7',
|
|
111
118
|
io: 'output',
|
|
112
119
|
reused: 'inline', // Don't use references by default (to support openapi conversion for google)
|
|
@@ -115,11 +122,13 @@ export function zodSchemaToJsonSchema(schema: ZodSchema, isOpenai: boolean = tru
|
|
|
115
122
|
// Zod v3 requires the zod-to-json-schema library
|
|
116
123
|
// Configuration adapted from Vercel AI SDK
|
|
117
124
|
// $refStrategy: 'none' is equivalent to v4's reused: 'inline'
|
|
118
|
-
|
|
125
|
+
result = zodToJsonSchemaV3(schema, {
|
|
119
126
|
target: isOpenai ? 'openAi' : 'jsonSchema7',
|
|
120
127
|
$refStrategy: 'none', // Don't use references by default (to support openapi conversion for google)
|
|
121
128
|
}) as JSONSchema7;
|
|
122
129
|
}
|
|
130
|
+
|
|
131
|
+
return strict ? (toStrictJsonSchema(result) as JSONSchema7) : result;
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
/**
|
package/src/log.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
// TODO(brian): PR4 - Add logging integration exports
|
|
6
|
+
// TODO(brian): PR5 - Add uploadSessionReport export
|
|
7
|
+
|
|
8
|
+
export * as traceTypes from './trace_types.js';
|
|
9
|
+
export { setTracerProvider, setupCloudTracer, tracer, type StartSpanOptions } from './traces.js';
|
|
10
|
+
export { recordException, recordRealtimeMetrics } from './utils.js';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
// LiveKit custom attributes
|
|
6
|
+
export const ATTR_SPEECH_ID = 'lk.speech_id';
|
|
7
|
+
export const ATTR_AGENT_LABEL = 'lk.agent_label';
|
|
8
|
+
export const ATTR_START_TIME = 'lk.start_time';
|
|
9
|
+
export const ATTR_END_TIME = 'lk.end_time';
|
|
10
|
+
export const ATTR_RETRY_COUNT = 'lk.retry_count';
|
|
11
|
+
|
|
12
|
+
export const ATTR_PARTICIPANT_ID = 'lk.participant_id';
|
|
13
|
+
export const ATTR_PARTICIPANT_IDENTITY = 'lk.participant_identity';
|
|
14
|
+
export const ATTR_PARTICIPANT_KIND = 'lk.participant_kind';
|
|
15
|
+
|
|
16
|
+
// session start
|
|
17
|
+
export const ATTR_JOB_ID = 'lk.job_id';
|
|
18
|
+
export const ATTR_AGENT_NAME = 'lk.agent_name';
|
|
19
|
+
export const ATTR_ROOM_NAME = 'lk.room_name';
|
|
20
|
+
export const ATTR_SESSION_OPTIONS = 'lk.session_options';
|
|
21
|
+
|
|
22
|
+
// assistant turn
|
|
23
|
+
export const ATTR_USER_INPUT = 'lk.user_input';
|
|
24
|
+
export const ATTR_INSTRUCTIONS = 'lk.instructions';
|
|
25
|
+
export const ATTR_SPEECH_INTERRUPTED = 'lk.interrupted';
|
|
26
|
+
|
|
27
|
+
// llm node
|
|
28
|
+
export const ATTR_CHAT_CTX = 'lk.chat_ctx';
|
|
29
|
+
export const ATTR_FUNCTION_TOOLS = 'lk.function_tools';
|
|
30
|
+
export const ATTR_RESPONSE_TEXT = 'lk.response.text';
|
|
31
|
+
export const ATTR_RESPONSE_FUNCTION_CALLS = 'lk.response.function_calls';
|
|
32
|
+
|
|
33
|
+
// function tool
|
|
34
|
+
export const ATTR_FUNCTION_TOOL_NAME = 'lk.function_tool.name';
|
|
35
|
+
export const ATTR_FUNCTION_TOOL_ARGS = 'lk.function_tool.arguments';
|
|
36
|
+
export const ATTR_FUNCTION_TOOL_IS_ERROR = 'lk.function_tool.is_error';
|
|
37
|
+
export const ATTR_FUNCTION_TOOL_OUTPUT = 'lk.function_tool.output';
|
|
38
|
+
|
|
39
|
+
// tts node
|
|
40
|
+
export const ATTR_TTS_INPUT_TEXT = 'lk.input_text';
|
|
41
|
+
export const ATTR_TTS_STREAMING = 'lk.tts.streaming';
|
|
42
|
+
export const ATTR_TTS_LABEL = 'lk.tts.label';
|
|
43
|
+
|
|
44
|
+
// eou detection
|
|
45
|
+
export const ATTR_EOU_PROBABILITY = 'lk.eou.probability';
|
|
46
|
+
export const ATTR_EOU_UNLIKELY_THRESHOLD = 'lk.eou.unlikely_threshold';
|
|
47
|
+
export const ATTR_EOU_DELAY = 'lk.eou.endpointing_delay';
|
|
48
|
+
export const ATTR_EOU_LANGUAGE = 'lk.eou.language';
|
|
49
|
+
export const ATTR_USER_TRANSCRIPT = 'lk.user_transcript';
|
|
50
|
+
export const ATTR_TRANSCRIPT_CONFIDENCE = 'lk.transcript_confidence';
|
|
51
|
+
export const ATTR_TRANSCRIPTION_DELAY = 'lk.transcription_delay';
|
|
52
|
+
export const ATTR_END_OF_TURN_DELAY = 'lk.end_of_turn_delay';
|
|
53
|
+
|
|
54
|
+
// metrics
|
|
55
|
+
export const ATTR_LLM_METRICS = 'lk.llm_metrics';
|
|
56
|
+
export const ATTR_TTS_METRICS = 'lk.tts_metrics';
|
|
57
|
+
export const ATTR_REALTIME_MODEL_METRICS = 'lk.realtime_model_metrics';
|
|
58
|
+
|
|
59
|
+
// OpenTelemetry GenAI attributes
|
|
60
|
+
// OpenTelemetry specification: https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/
|
|
61
|
+
export const ATTR_GEN_AI_OPERATION_NAME = 'gen_ai.operation.name';
|
|
62
|
+
export const ATTR_GEN_AI_REQUEST_MODEL = 'gen_ai.request.model';
|
|
63
|
+
export const ATTR_GEN_AI_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens';
|
|
64
|
+
export const ATTR_GEN_AI_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens';
|
|
65
|
+
|
|
66
|
+
// Unofficial OpenTelemetry GenAI attributes, recognized by LangFuse
|
|
67
|
+
// https://langfuse.com/integrations/native/opentelemetry#usage
|
|
68
|
+
// but not yet in the official OpenTelemetry specification.
|
|
69
|
+
export const ATTR_GEN_AI_USAGE_INPUT_TEXT_TOKENS = 'gen_ai.usage.input_text_tokens';
|
|
70
|
+
export const ATTR_GEN_AI_USAGE_INPUT_AUDIO_TOKENS = 'gen_ai.usage.input_audio_tokens';
|
|
71
|
+
export const ATTR_GEN_AI_USAGE_INPUT_CACHED_TOKENS = 'gen_ai.usage.input_cached_tokens';
|
|
72
|
+
export const ATTR_GEN_AI_USAGE_OUTPUT_TEXT_TOKENS = 'gen_ai.usage.output_text_tokens';
|
|
73
|
+
export const ATTR_GEN_AI_USAGE_OUTPUT_AUDIO_TOKENS = 'gen_ai.usage.output_audio_tokens';
|
|
74
|
+
|
|
75
|
+
// OpenTelemetry GenAI event names (for structured logging)
|
|
76
|
+
export const EVENT_GEN_AI_SYSTEM_MESSAGE = 'gen_ai.system.message';
|
|
77
|
+
export const EVENT_GEN_AI_USER_MESSAGE = 'gen_ai.user.message';
|
|
78
|
+
export const EVENT_GEN_AI_ASSISTANT_MESSAGE = 'gen_ai.assistant.message';
|
|
79
|
+
export const EVENT_GEN_AI_TOOL_MESSAGE = 'gen_ai.tool.message';
|
|
80
|
+
export const EVENT_GEN_AI_CHOICE = 'gen_ai.choice';
|
|
81
|
+
|
|
82
|
+
// Exception attributes
|
|
83
|
+
export const ATTR_EXCEPTION_TRACE = 'exception.stacktrace';
|
|
84
|
+
export const ATTR_EXCEPTION_TYPE = 'exception.type';
|
|
85
|
+
export const ATTR_EXCEPTION_MESSAGE = 'exception.message';
|
|
86
|
+
|
|
87
|
+
// Platform-specific attributes
|
|
88
|
+
export const ATTR_LANGFUSE_COMPLETION_START_TIME = 'langfuse.observation.completion_start_time';
|