UNPKG

adk-typescript

Version:

TypeScript port of Google's Agent Development Kit (ADK)

190 lines (189 loc) 7.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GeminiLlmConnection = void 0; const BaseLlmConnection_1 = require("./BaseLlmConnection"); const LlmResponse_1 = require("./LlmResponse"); /** * The Gemini model connection. */ class GeminiLlmConnection extends BaseLlmConnection_1.BaseLlmConnection { /** * Constructor for GeminiLlmConnection * @param geminiSession The Gemini async session */ constructor(geminiSession) { super(); this.geminiSession = geminiSession; } /** * Sends the conversation history to the gemini model. * * You call this method right after setting up the model connection. * The model will respond if the last content is from user, otherwise it will * wait for new user input before responding. * * @param history The conversation history to send to the model. */ async sendHistory(history) { // Filter out any content without text parts (e.g., ignore audio during agent transfer) const contents = history.filter(content => content.parts && content.parts.length > 0 && content.parts.some(part => part.text)); if (contents.length > 0) { await this.geminiSession.send({ turns: contents, turn_complete: contents[contents.length - 1].role === 'user' }); } else { console.info('No content is sent'); } } /** * Sends a user content to the gemini model. * * The model will respond immediately upon receiving the content. * If you send function responses, all parts in the content should be function * responses. * * @param content The content to send to the model. */ async sendContent(content) { if (!content.parts || content.parts.length === 0) { throw new Error('Content must have parts'); } if (content.parts[0].functionResponse) { // All parts have to be function responses const functionResponses = content.parts .map(part => part.functionResponse) .filter(fr => fr !== undefined); console.debug('Sending LLM function response:', functionResponses); await this.geminiSession.send({ function_responses: functionResponses }); } else { await this.geminiSession.send({ turns: [content], turn_complete: true }); } } /** * Sends a chunk of audio or a frame of video to the model in realtime. * * The model may not respond immediately upon receiving the blob. It will do * voice activity detection and decide when to respond. * * @param blob The blob to send to the model. */ async sendRealtime(blob) { console.debug('Sending LLM Blob:', blob); await this.geminiSession.send(blob); } /** * Builds a full text response. * * The text is not partial and the returned LlmResponse is not be * partial. * * @param text The text to be included in the response. * @returns An LlmResponse containing the full text. */ buildFullTextResponse(text) { const response = new LlmResponse_1.LlmResponse(); response.content = { role: 'model', parts: [{ text }] }; return response; } /** * Receives the model response using the llm server connection. * * @returns An async generator yielding LlmResponse objects. */ async *receive() { let text = ''; for await (const message of this.geminiSession.receive()) { console.debug('Got LLM Live message:', message); if (message.server_content) { const serverContent = message.server_content; const content = serverContent.model_turn; if (content && content.parts && content.parts.length > 0) { const response = new LlmResponse_1.LlmResponse(); response.content = content; response.interrupted = serverContent.interrupted; if (content.parts[0].text) { text += content.parts[0].text; response.partial = true; } // Don't yield the merged text event when receiving audio data else if (text && !content.parts[0].inlineData) { yield this.buildFullTextResponse(text); text = ''; } yield response; } // Handle transcription if (serverContent.output_transcription && serverContent.output_transcription.text) { // Transcription is always considered as partial event // We rely on other control signals to determine when to yield the // full text response (turn_complete, interrupted, or tool_call) text += serverContent.output_transcription.text; const response = new LlmResponse_1.LlmResponse(); response.content = { role: 'model', parts: [{ text: serverContent.output_transcription.text }] }; response.partial = true; yield response; } // Handle turn complete if (serverContent.turn_complete) { if (text) { yield this.buildFullTextResponse(text); text = ''; } const response = new LlmResponse_1.LlmResponse(); response.turnComplete = true; response.interrupted = serverContent.interrupted; yield response; break; } // In case of empty content or parts, we still surface it // If it's an interrupted message, we merge the previous partial text if (serverContent.interrupted && text) { yield this.buildFullTextResponse(text); text = ''; } // Yield interrupted status const interruptedResponse = new LlmResponse_1.LlmResponse(); interruptedResponse.interrupted = serverContent.interrupted; yield interruptedResponse; } // Handle tool call if (message.tool_call) { if (text) { yield this.buildFullTextResponse(text); text = ''; } const parts = message.tool_call.function_calls.map(functionCall => ({ functionCall })); const response = new LlmResponse_1.LlmResponse(); response.content = { role: 'model', parts }; yield response; } } } /** * Closes the llm server connection. */ async close() { await this.geminiSession.close(); } } exports.GeminiLlmConnection = GeminiLlmConnection;