// ============================================================================== // LeadScout AI - Backend Serverless Function // File: /netlify/functions/run-agent.js // // Description: // This is the secure backend for your web application. It receives requests // from the frontend, securely adds your API keys (which are stored as // secret environment variables), and calls the Google and Tavily APIs. // This ensures your keys are NEVER exposed to the public. // ============================================================================== // We are using the Tavily and Google AI SDKs for Node.js const { TavilyClient } = require('@tavily/tavily-node'); const { GoogleGenerativeAI } = require('@google/generative-ai'); exports.handler = async function (event) { // --- Step 1: Get data from the frontend request --- const { query, numLeads } = JSON.parse(event.body); // --- Step 2: Access your secret API keys --- // These are stored securely in your Netlify account, not in the code. const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; const TAVILY_API_KEY = process.env.TAVILY_API_KEY; if (!GOOGLE_API_KEY || !TAVILY_API_KEY) { return { statusCode: 500, body: JSON.stringify({ error: "API keys are not configured on the server." }), }; } // --- Step 3: Initialize the AI clients --- const tavily = new TavilyClient(TAVILY_API_KEY); const genAI = new GoogleGenerativeAI(GOOGLE_API_KEY); const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); // --- Step 4: Define the agent's logic --- const toolName = "TavilySearchResults"; const promptTemplate = ` You are a lead generation assistant for a labour hire company. Your mission is to find potential clients for a specified role and location. For each request, you MUST perform these steps: 1. Use your search tool to find exactly ${numLeads} companies that are likely to hire for the requested role in the specified area. 2. For each company, you MUST find the Company Name and a main Phone Number. If you cannot find a phone number for a company, you MUST discard it and find a different one until you have met the requested number of leads. 3. Present your final answer as a simple, clean, numbered list of the companies and their phone numbers. Do not include any other information. You have access to the following tools: - ${toolName}(query: str): A search engine. Use specific queries like "plumbing companies Noosa phone number". Use the following format: Question: ${query} Thought: I need to find businesses that hire for the specified role and location. My goal is to find exactly ${numLeads} of them. I will use my search tool to find their name and phone number. Action: ${toolName}(query="A specific search query") Observation: The result of the search action. ... (this can repeat) Thought: I have a complete list of ${numLeads} companies and their phone numbers. I am ready to present the final answer. Final Answer: (Your clean, numbered list of Company Name - Phone Number).`; // --- Step 5: Run the agent loop --- let fullPrompt = promptTemplate; let finalAnswer = "Agent did not produce a final answer."; for (let i = 0; i < 7; i++) { // Max 7 loops to prevent infinite execution try { const result = await model.generateContent(fullPrompt); const responseText = result.response.text(); if (responseText.includes("Final Answer:")) { finalAnswer = responseText.split("Final Answer:")[1].trim(); break; // Exit loop on success } const actionMatch = responseText.match(/Action:\s*(\w+)\s*\(\s*query=(?:'|")([^'"]+)(?:'|")\s*\)/); if (actionMatch) { const searchResults = await tavily.search(actionMatch[2], { maxResults: 5 }); const observation = JSON.stringify(searchResults.results.map(r => ({ title: r.title, content: r.content }))); fullPrompt += `\n${responseText}\nObservation: ${observation}`; } else { finalAnswer = responseText; // If no action, return the text break; } } catch (error) { console.error("Error in agent loop:", error); return { statusCode: 500, body: JSON.stringify({ error: `An error occurred while running the agent: ${error.message}` }), }; } } // --- Step 6: Send the result back to the frontend --- return { statusCode: 200, body: JSON.stringify({ answer: finalAnswer }), }; };