Agricrop / lib /maps-grounding.ts
Rohanbagulwar
Commitingcode
7c2c194
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import { GoogleGenAI, GenerateContentResponse } from '@google/genai';
import { useMapStore } from '@/lib/state';
// TODO - replace with appropriate key
// const API_KEY = process.env.GEMINI_API_KEY
const API_KEY = process.env.API_KEY as string;
// Agricultural parameters interface
export interface AgriculturalParameters {
// Required parameters (5)
latitude: number;
longitude: number;
soilType: 'clay' | 'sandy' | 'loamy' | 'silt' | 'peat';
climate: 'tropical' | 'arid' | 'temperate' | 'continental' | 'polar';
season: 'spring' | 'summer' | 'fall' | 'winter';
// Optional parameters (5)
rainfall?: number; // Annual rainfall in mm
temperature?: number; // Average temperature in °C
irrigationAvailable?: boolean;
farmSize?: number; // Farm size in hectares
multiCrop?: string;
}
const AGRICULTURAL_SYS_INSTRUCTIONS = `You are an expert agricultural advisor AI. Based on the provided location coordinates and agricultural parameters (soil type, climate, season, rainfall, temperature, irrigation, farm size), provide detailed crop recommendations. Include:
1. One line description of the location and its key agricultural conditions
2. Top 3 recommended crops with rationale (if farmer is multiCrop is Yes, suggest intercropping options with percentage area allocation else show only one crop)
3. Expected yield estimates
4. Soil preparation requirements (suggest if soil testing is needed)
5. Water and fertilizer needs (Should specify if irrigation is needed)
6. Potential challenges and mitigation strategies
Format your response in clear sections in JSON format. It should be having one line values for easy parsing.`;
/**
* Helper function to automatically zoom the map to a specific location
* @param latitude - The latitude coordinate
* @param longitude - The longitude coordinate
*/
function zoomToLocation(latitude: number, longitude: number): void {
const { setCameraTarget, setPreventAutoFrame } = useMapStore.getState();
// Set camera target for field-level view
setCameraTarget({
center: {
lat: latitude,
lng: longitude,
altitude: 750 // 750m altitude for good field detail
},
range: 2500, // 2.5km range for field-level view
tilt: 50, // 50° tilt for better terrain view
heading: 0,
roll: 0,
});
// Prevent auto-framing to maintain our specific zoom level
setPreventAutoFrame(true);
}
/**
* Calls the Gemini API with the googleSearch tool to get a grounded response.
* @param prompt The user's text prompt.
* @returns An object containing the model's text response and grounding sources.
*/
export async function fetchMapsGroundedResponseSDK({
prompt,
enableWidget = true,
lat,
lng,
systemInstruction,
}: {
prompt: string;
enableWidget?: boolean;
lat?: number;
lng?: number;
systemInstruction?: string;
}): Promise<GenerateContentResponse> {
if (!API_KEY) {
throw new Error('Missing required environment variable: API_KEY');
}
try {
const ai = new GoogleGenAI({apiKey: API_KEY});
const request: any = {
model: 'gemini-2.5-flash',
contents: prompt,
config: {
tools: [{googleMaps: {}}],
thinkingConfig: {
thinkingBudget: 0,
},
systemInstruction: systemInstruction || AGRICULTURAL_SYS_INSTRUCTIONS,
},
};
if (lat !== undefined && lng !== undefined) {
request.toolConfig = {
retrievalConfig: {
latLng: {
latitude: lat,
longitude: lng,
},
},
};
}
const response = await ai.models.generateContent(request);
return (response);
} catch (error) {
console.error(`Error calling Google Search grounding: ${error}
With prompt: ${prompt}`);
// Re-throw the error to be handled by the caller
throw error;
}
}
/**
* Calls the Google AI Platform REST API to get a Maps-grounded response.
* @param options The request parameters.
* @returns A promise that resolves to the API's GenerateContentResponse.
*/
export async function fetchMapsGroundedResponseREST({
prompt,
enableWidget = true,
lat,
lng,
systemInstruction,
}: {
prompt: string;
enableWidget?: boolean;
lat?: number;
lng?: number;
systemInstruction?: string;
}): Promise<GenerateContentResponse> {
if (!API_KEY) {
throw new Error('Missing required environment variable: API_KEY');
}
const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent`;
const requestBody: any = {
contents: [
{
parts: [
{
text: prompt,
},
],
},
],
system_instruction: {
parts: [ { text: systemInstruction || AGRICULTURAL_SYS_INSTRUCTIONS } ]
},
tools: [
{
google_maps: {
enable_widget: enableWidget
},
},
],
generationConfig: {
thinkingConfig: {
thinkingBudget: 0
}
}
};
if (lat !== undefined && lng !== undefined) {
requestBody.toolConfig = {
retrievalConfig: {
latLng: {
latitude: lat,
longitude: lng,
},
},
};
}
try {
// console.log(`endpoint: ${endpoint}\nbody: ${JSON.stringify(requestBody, null, 2)}`)
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-goog-api-key': API_KEY,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorBody = await response.text();
console.error('Error from Generative Language API:', errorBody);
throw new Error(
`API request failed with status ${response.status}: ${errorBody}`,
);
}
const data = await response.json();
return data as GenerateContentResponse;
} catch (error) {
console.error(`Error calling Maps grounding REST API: ${error}`);
throw error;
}
}
/**
* Calls the Google AI Platform REST API to get agricultural recommendations.
* @param params The agricultural parameters and location data.
* @returns A promise that resolves to the API's GenerateContentResponse.
*/
export async function fetchAgriculturalRecommendations(
params: AgriculturalParameters
): Promise<GenerateContentResponse> {
if (!API_KEY) {
throw new Error('Missing required environment variable: API_KEY');
}
const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent`;
// Construct agricultural prompt with all parameters
const agriculturalPrompt = `Location: ${params.latitude}, ${params.longitude}
Soil Type: ${params.soilType}
Climate: ${params.climate}
Season: ${params.season}
${params.rainfall ? `Annual Rainfall: ${params.rainfall}mm` : ''}
${params.temperature ? `Average Temperature: ${params.temperature}°C` : ''}
${params.irrigationAvailable !== undefined ? `Irrigation Available: ${params.irrigationAvailable ? 'Yes' : 'No'}` : ''}
${params.farmSize ? `Farm Size: ${params.farmSize} hectares` : ''}
${params.multiCrop ? `Multi Crop: ${params.multiCrop}` : ''}
Please provide detailed crop recommendations for this agricultural location in strict JSON format as per the system instructions. `;
const requestBody: any = {
contents: [
{
parts: [
{
text: agriculturalPrompt,
},
],
},
],
system_instruction: {
parts: [ { text: AGRICULTURAL_SYS_INSTRUCTIONS } ]
},
tools: [
{
google_maps: {
enable_widget: true
},
},
],
generationConfig: {
thinkingConfig: {
thinkingBudget: 0
}
}
};
// Add location context for Maps grounding
requestBody.toolConfig = {
retrievalConfig: {
latLng: {
latitude: params.latitude,
longitude: params.longitude,
},
},
};
try {
// console.log(`endpoint: ${endpoint}\nbody: ${JSON.stringify(requestBody, null, 2)}`)
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-goog-api-key': API_KEY,
},
body: JSON.stringify(requestBody),
});
console.log('Agricultural Recommendations API call req :', requestBody);
if (!response.ok) {
const errorBody = await response.text();
console.error('Error from Generative Language API:', errorBody);
throw new Error(
`API request failed with status ${response.status}: ${errorBody}`,
);
}
else{
console.log('Agricultural Recommendations API call successful.',response);
}
const data = await response.json();
// Automatically zoom to the location after getting the response
zoomToLocation(params.latitude, params.longitude);
console.log('Agricultural Recommendations Response:', data);
return data as GenerateContentResponse;
} catch (error) {
console.error(`Error calling Agricultural Recommendations API: ${error}`);
throw error;
}
}