Can anyone help me to use a custom function and the google search tool with gemini-2.0-flash-exp in node? I am always getting the error “Requst contains an invalid argument” Am using the npm package Using one of these tools by itself works fine. Here;s my code (edited)
class UnifiedGeminiAPI {
constructor(options = {}) {
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error("GEMINI_API_KEY not found in environment variables.");
}
this.genAI = new GoogleGenerativeAI(apiKey);
this.model = this.genAI.getGenerativeModel({ model: "gemini-2.0-flash-exp" });
this.debug = options.debug || false;
this.defaultSystemPrompt = options.systemPrompt || `You are Professor Regenbogen, an expert LGBTQ+ journalist and content writer for Pride.Direct.
Your expertise is in creating engaging, well-researched articles about LGBTQ+ topics for a German-speaking audience.`;
// Define function declarations for blog publishing
this.functionDeclarations = [{
name: "publish_blog_post",
description: "Publishes a blog post to Shopify. Only use when content is ready and user has confirmed publication.",
parameters: {
type: "object",
properties: {
title: {
type: "string",
description: "The title of the blog post"
},
author: {
type: "string",
description: "The author of the blog post"
},
content: {
type: "string",
description: "The HTML content of the blog post"
},
tags: {
type: "array",
items: { type: "string" },
description: "Array of tags for the blog post"
},
published: {
type: "boolean",
description: "Whether to publish immediately"
},
userConfirmed: {
type: "boolean",
description: "Whether user has confirmed publication"
}
},
required: ["title", "author", "content", "userConfirmed"]
}
}];
}
log(...args) {
if (this.debug) {
console.log('[DEBUG]', ...args);
}
}
async ask(text, options = {}) {
try {
// Build the complete request payload
const requestPayload = {
contents: [{ role: 'user', parts: [{ text: `${options.systemPrompt || this.defaultSystemPrompt}\n\n${text}` }] }],
tools: [
{ google_search: {} }, // Search tool
{ function_declarations: this.functionDeclarations } // Reuse the class property
],
generationConfig: {
maxOutputTokens: 2048,
},
safetySettings: [
{
category: "HARM_CATEGORY_HARASSMENT",
threshold: "BLOCK_NONE"
},
{
category: "HARM_CATEGORY_HATE_SPEECH",
threshold: "BLOCK_NONE"
},
{
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold: "BLOCK_NONE"
},
{
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: "BLOCK_NONE"
}
]
};
// Log the complete request payload
this.log('Sending request to Gemini:');
this.log(JSON.stringify(requestPayload, null, 2));
const prompt = `${options.systemPrompt || this.defaultSystemPrompt}\n\n${text}`;
const result = await this.model.generateContent(requestPayload);
if (!result?.response?.candidates?.[0]?.content?.parts) {
throw new Error('Invalid response format from Gemini');
}
// Get the response content
const responsePart = result.response.candidates[0].content.parts[0];
// Check if we have a function call
if (responsePart.functionCall) {
this.log('Function call detected:', responsePart.functionCall);
const { name, args } = responsePart.functionCall;
if (name === 'publish_blog_post') {
if (!args.userConfirmed) {
return responsePart.text + '\n\nWould you like to publish this article? Please confirm.';
}
this.log('Publishing blog post...');
const result = await createBlogArticle(args);
return responsePart.text + `\n\nBlog post published successfully with ID: ${result.id}`;
}
}
// Get any search citations if available
let response = responsePart.text || '';
if (result.response.candidates[0].citations) {
response += '\n\nSources:\n';
result.response.candidates[0].citations.forEach((citation, index) => {
response += `[${index + 1}] ${citation.url}\n`;
});
}
return response;
} catch (error) {
console.error('Error querying Gemini:', error);
throw error;
}
}
}