UNPKG

@robota-sdk/google

Version:

Google AI integration for Robota SDK - Gemini Pro, Gemini Flash, function calling, and tool integration with Google's Generative AI

349 lines (344 loc) 10.6 kB
'use strict'; require('@google/generative-ai'); var core = require('@robota-sdk/core'); // src/provider.ts // src/adapter.ts var GoogleConversationAdapter = class { /** * Convert UniversalMessage array to Google AI message format */ static toGoogleFormat(messages) { return messages.filter((msg) => msg.role !== "system").map((msg) => this.convertMessage(msg)); } /** * Convert a single UniversalMessage to Google AI format */ static convertMessage(msg) { const messageRole = msg.role; if (messageRole === "user") { const userMsg = msg; return { role: "user", parts: [{ text: userMsg.content }] }; } if (messageRole === "assistant") { const assistantMsg = msg; if (assistantMsg.toolCalls) { const parts = [{ text: assistantMsg.content || "" }]; const toolCalls = assistantMsg.toolCalls; for (const tc of toolCalls) { parts.push({ functionCall: { name: tc.function.name, args: JSON.parse(tc.function.arguments) } }); } return { role: "model", parts }; } return { role: "model", parts: [{ text: assistantMsg.content || "" }] }; } if (messageRole === "tool") { const toolMsg = msg; return { role: "function", parts: [ { functionResponse: { name: toolMsg.name || "unknown", response: toolMsg.content } } ] }; } if (messageRole === "system") { const systemMsg = msg; return { role: "user", parts: [{ text: `[System]: ${systemMsg.content}` }] }; } const _exhaustiveCheck = msg; return _exhaustiveCheck; } /** * Extract system messages and combine them as system instruction */ static extractSystemInstruction(messages, fallbackSystemPrompt) { const systemMessages = messages.filter((msg) => msg.role === "system"); if (systemMessages.length > 0) { return systemMessages.map((msg) => msg.content).join("\n\n"); } return fallbackSystemPrompt; } /** * Complete message conversion pipeline */ static processMessages(messages, systemPrompt) { const systemInstruction = this.extractSystemInstruction(messages, systemPrompt); const contents = this.toGoogleFormat(messages); return { contents, systemInstruction }; } }; // src/provider.ts var GoogleProvider = class extends core.BaseAIProvider { /** * Provider identifier name * @readonly */ name = "google"; /** * Google AI client instance * @internal */ client; /** * Provider configuration options * @readonly */ options; /** * Create a new Google AI provider instance * * @param options - Configuration options for the Google provider * * @throws {Error} When client is not provided in options */ constructor(options) { super(); this.options = { temperature: 0.7, maxTokens: void 0, ...options }; if (!options.client) { throw new Error("Google AI client is not injected. The client option is required."); } this.client = options.client; } /** * Send a chat request to Google AI and receive a complete response * * @param model - Model name to use (e.g., 'gemini-1.5-pro', 'gemini-1.5-flash') * @param context - Context object containing messages and system prompt * @param options - Optional generation parameters and tools * @returns Promise resolving to the model's response * * @throws {Error} When context is invalid * @throws {Error} When messages array is invalid * @throws {Error} When Google AI API call fails */ async chat(model, context, options) { this.validateContext(context); const { messages, systemPrompt } = context; try { const { contents, systemInstruction } = GoogleConversationAdapter.processMessages( messages, systemPrompt ); const toolConfig = this.configureTools(options?.tools); const modelConfig = { model: model || this.options.model || "gemini-1.5-flash", systemInstruction }; if (toolConfig) { modelConfig.tools = toolConfig.tools; } const generativeModel = this.client.getGenerativeModel(modelConfig); const generationConfig = { temperature: options?.temperature ?? this.options.temperature, maxOutputTokens: options?.maxTokens ?? this.options.maxTokens, ...this.options.responseMimeType && { responseMimeType: this.options.responseMimeType }, ...this.options.responseSchema && { responseSchema: this.options.responseSchema } }; const result = await generativeModel.generateContent({ contents, generationConfig }); return this.parseResponse(result); } catch (error) { this.handleApiError(error, "chat"); } } /** * Send a streaming chat request to Google AI and receive response chunks * * Generates an async iterator that yields response chunks as they arrive. * Useful for real-time display of responses or handling large responses incrementally. * * @param model - Model name to use * @param context - Context object containing messages and system prompt * @param options - Optional generation parameters and tools * @returns Async generator yielding response chunks * * @throws {Error} When context is invalid * @throws {Error} When messages array is invalid * @throws {Error} When Google AI API streaming call fails */ async *chatStream(model, context, options) { this.validateContext(context); const { messages, systemPrompt } = context; try { const { contents, systemInstruction } = GoogleConversationAdapter.processMessages( messages, systemPrompt ); const toolConfig = this.configureTools(options?.tools); const modelConfig = { model: model || this.options.model || "gemini-1.5-flash", systemInstruction }; if (toolConfig) { modelConfig.tools = toolConfig.tools; } const generativeModel = this.client.getGenerativeModel(modelConfig); const generationConfig = { temperature: options?.temperature ?? this.options.temperature, maxOutputTokens: options?.maxTokens ?? this.options.maxTokens, ...this.options.responseMimeType && { responseMimeType: this.options.responseMimeType }, ...this.options.responseSchema && { responseSchema: this.options.responseSchema } }; const result = await generativeModel.generateContentStream({ contents, generationConfig }); for await (const chunk of result.stream) { yield this.parseStreamingChunk(chunk); } } catch (error) { this.handleApiError(error, "chatStream"); } } /** * Configure tools for Google AI API request * * Google AI supports function calling with Gemini models. * Transforms function schemas into Google AI tool format. * * @param tools - Array of function schemas * @returns Google AI tool configuration object or undefined */ configureTools(tools) { if (!tools || !Array.isArray(tools)) { return void 0; } return { tools: [{ functionDeclarations: tools.map((fn) => ({ name: fn.name, description: fn.description || "", parameters: fn.parameters })) }] }; } /** * Parse Google AI response into universal ModelResponse format * * Extracts content, usage information, and metadata from the Google AI response * and converts it to the standard format used across all providers. * Supports function calling with Gemini models. * * @param response - Raw response from Google AI API * @returns Parsed model response in universal format * * @internal */ parseResponse(response) { let content = ""; const toolCalls = []; const candidate = response.response?.candidates?.[0]; if (candidate?.content?.parts) { for (const part of candidate.content.parts) { if (part.text) { content += part.text; } else if (part.functionCall) { toolCalls.push({ id: `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, type: "function", function: { name: part.functionCall.name, arguments: JSON.stringify(part.functionCall.args || {}) } }); } } } const usageMetadata = response.response?.usageMetadata; const usage = usageMetadata ? { promptTokens: usageMetadata.promptTokenCount || 0, completionTokens: usageMetadata.candidatesTokenCount || 0, totalTokens: usageMetadata.totalTokenCount || 0 } : { promptTokens: 0, completionTokens: 0, totalTokens: 0 }; const result = { content: content || void 0, usage, metadata: { model: response.response?.model, finishReason: candidate?.finishReason, safetyRatings: candidate?.safetyRatings } }; if (toolCalls.length > 0) { result.toolCalls = toolCalls; } return result; } /** * Parse Google AI streaming response chunk into universal format * * Converts individual chunks from the streaming response into the standard * StreamingResponseChunk format used across all providers. * * @param chunk - Raw chunk from Google AI streaming API * @returns Parsed streaming response chunk * * @internal */ parseStreamingChunk(chunk) { const text = chunk.text() || ""; const candidate = chunk.candidates?.[0]; const isComplete = candidate?.finishReason !== void 0 && candidate.finishReason !== null; return { content: text, isComplete }; } /** * Release resources and close connections * * Performs cleanup operations when the provider is no longer needed. * Google AI client doesn't require explicit cleanup, so this is a no-op. * * @returns Promise that resolves when cleanup is complete */ async close() { } }; exports.GoogleConversationAdapter = GoogleConversationAdapter; exports.GoogleProvider = GoogleProvider; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map