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
5 changes: 5 additions & 0 deletions .changeset/instructions-template-collapse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@livekit/agents': patch
---

Fix modality-aware instruction templates to collapse identical variants and avoid duplicate rendered output.
39 changes: 39 additions & 0 deletions agents/src/llm/chat_context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,45 @@ extra`;
expect(instr.text).toBe('null=null undefined=undefined');
});

it('tpl renders each modality variant exactly once', () => {
const instr = Instructions.tpl`${'You are a helpful assistant.'}

${new Instructions({ audio: 'Handle noisy voice input.', text: 'Handle typed input.' })}`;

expect(renderInstructions(instr, 'audio')).toBe(
'You are a helpful assistant.\n\nHandle noisy voice input.',
);
expect(renderInstructions(instr, 'text')).toBe(
'You are a helpful assistant.\n\nHandle typed input.',
);
expect(renderInstructions(instr, 'audio').split('You are a helpful assistant.')).toHaveLength(
2,
);
});

it('tpl without Instructions interpolations is an audio-only render', () => {
const instr = Instructions.tpl`Hello ${'Alex'}`;

expect(instr.toJSON()).toEqual({ type: 'instructions', audio: 'Hello Alex' });
expect(instr.audio).toBe('Hello Alex');
expect(instr.text).toBe('Hello Alex');
expect(renderInstructions(instr)).toBe('Hello Alex');
expect(renderInstructions(instr, 'audio')).toBe('Hello Alex');
});

it('tpl collapses identical modality variants', () => {
const instr = Instructions.tpl`${'You are a helpful assistant.'}

${new Instructions({ audio: 'shared note', text: 'shared note' })}`;

expect(instr.toJSON()).toEqual({
type: 'instructions',
audio: 'You are a helpful assistant.\n\nshared note',
});
expect(renderInstructions(instr)).toBe('You are a helpful assistant.\n\nshared note');
expect(renderInstructions(instr, 'audio')).toBe('You are a helpful assistant.\n\nshared note');
});

it('serializes to a dict with both variants and round-trips through toJSON', () => {
const instr = new Instructions({ audio: 'audio variant', text: 'text variant' });

Expand Down
9 changes: 4 additions & 5 deletions agents/src/llm/chat_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,12 @@ export class Instructions {
return result;
};

const hasTextVariant = values.some(
(value) => isInstructions(value) && value._textVariant !== undefined,
);
const audio = render('audio');
const text = render('text');

return new Instructions({
audio: render('audio'),
text: hasTextVariant ? render('text') : undefined,
audio,
text: audio === text ? undefined : text,
represent: render('value'),
});
}
Expand Down
Loading