diff --git a/.changeset/lemon-stars-float.md b/.changeset/lemon-stars-float.md new file mode 100644 index 000000000..7018fbf2c --- /dev/null +++ b/.changeset/lemon-stars-float.md @@ -0,0 +1,6 @@ +--- +'@livekit/agents-plugin-phonic': patch +'@livekit/agents': patch +--- + +add `preserveFunctionCallHistory` option to `AgentTask` and `TaskGroup` and use function call history in Phonic plugin diff --git a/agents/src/beta/workflows/task_group.ts b/agents/src/beta/workflows/task_group.ts index 8147c814b..8c96790dd 100644 --- a/agents/src/beta/workflows/task_group.ts +++ b/agents/src/beta/workflows/task_group.ts @@ -37,6 +37,7 @@ export interface TaskGroupOptions { returnExceptions?: boolean; chatCtx?: ChatContext; onTaskCompleted?: (event: TaskCompletedEvent) => Promise; + preserveFunctionCallHistory?: boolean; } export class TaskGroup extends AgentTask { @@ -48,9 +49,15 @@ export class TaskGroup extends AgentTask { private _currentTask?: AgentTask; constructor(options: TaskGroupOptions = {}) { - const { summarizeChatCtx = true, returnExceptions = false, chatCtx, onTaskCompleted } = options; - - super({ instructions: '*empty*', chatCtx }); + const { + summarizeChatCtx = true, + returnExceptions = false, + chatCtx, + onTaskCompleted, + preserveFunctionCallHistory = false, + } = options; + + super({ instructions: '*empty*', chatCtx, preserveFunctionCallHistory }); this._summarizeChatCtx = summarizeChatCtx; this._returnExceptions = returnExceptions; diff --git a/agents/src/voice/agent.ts b/agents/src/voice/agent.ts index ab7675523..839796f69 100644 --- a/agents/src/voice/agent.ts +++ b/agents/src/voice/agent.ts @@ -529,12 +529,23 @@ export class Agent { }; } +export interface AgentTaskOptions extends AgentOptions { + preserveFunctionCallHistory?: boolean; +} + export class AgentTask extends Agent { private started = false; private future = new Future(); + private _preserveFunctionCallHistory: boolean; #logger = log(); + constructor(options: AgentTaskOptions) { + const { preserveFunctionCallHistory = false, ...rest } = options; + super(rest); + this._preserveFunctionCallHistory = preserveFunctionCallHistory; + } + get done(): boolean { return this.future.done; } @@ -648,7 +659,7 @@ export class AgentTask extends Agent { if (!this.configSent) { if (chatCtx.items.length > 0) { - const turnHistory = chatCtx.items - .filter( - (item): item is llm.ChatMessage => - item.type === 'message' && - 'textContent' in item && - item.textContent !== undefined && - item.textContent.trim() !== '', - ) - .map((item) => `${item.role}: ${item.textContent}`) - .join('\n'); - if (turnHistory.trim() !== '') { + const turnHistory = this.buildTurnHistory(chatCtx); + if (turnHistory) { this.#logger.debug( 'updateChatCtx called with messages prior to config being sent to Phonic. Including conversation state in system instructions.', ); @@ -894,16 +886,13 @@ export class RealtimeSession extends llm.RealtimeSession { } private buildTurnHistory(chatCtx: llm.ChatContext): string | undefined { - const messages = chatCtx.items.filter( - (item): item is llm.ChatMessage => - item.type === 'message' && - 'textContent' in item && - item.textContent !== undefined && - item.textContent.trim() !== '', - ); - if (messages.length === 0) return undefined; - const history = messages.map((m) => `${m.role}: ${m.textContent}`).join('\n'); - return history.trim() || undefined; + const lines: string[] = []; + for (const item of chatCtx.items) { + const text = chatItemToText(item); + if (text) lines.push(text); + } + if (lines.length === 0) return undefined; + return lines.join('\n'); } private *resampleAudio(frame: AudioFrame): Generator { @@ -936,3 +925,19 @@ export class RealtimeSession extends llm.RealtimeSession { } } } + +function chatItemToText(item: llm.ChatItem): string | undefined { + if (item.type === 'message') { + const text = item.textContent?.trim(); + if (!text) return undefined; + return `<${item.role}>${text}`; + } + if (item.type === 'function_call') { + return `${item.args}`; + } + if (item.type === 'function_call_output') { + const tag = item.isError ? 'tool_error' : 'tool_output'; + return `<${tag} name="${item.name}">${item.output.slice(0, TOOL_CALL_OUTPUT_MAX_CHARS_FOR_HISTORY)}`; + } + return undefined; +}