From bbec51a43bb522651612d2619e87f180520b52d1 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 31 May 2026 09:14:44 +0000
Subject: [PATCH] Restructure resolutionSearchSchema for xAI compatibility
- Extract resolutionSearchSchema to lib/schema/resolution-search.ts
- Flatten schema structure to reduce nesting for better xAI compatibility
- Replace z.literal with z.string to avoid JSON Schema 'const' constraints
- Use explicit union types for coordinates instead of z.any()
- Make geoJson field optional
- Update agent prompt and field names
- Add reconstruction logic in app/actions.tsx to maintain UI compatibility with standard GeoJSON
Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
---
app/actions.tsx | 24 +++++++++++++++++--
lib/agents/resolution-search.tsx | 39 ++----------------------------
lib/schema/resolution-search.ts | 41 ++++++++++++++++++++++++++++++++
3 files changed, 65 insertions(+), 39 deletions(-)
create mode 100644 lib/schema/resolution-search.ts
diff --git a/app/actions.tsx b/app/actions.tsx
index 50e985bf..167d23d4 100644
--- a/app/actions.tsx
+++ b/app/actions.tsx
@@ -114,11 +114,30 @@ async function submit(formData?: FormData, skip?: boolean) {
const analysisResult = await streamResult.object;
summaryStream.done(analysisResult.summary || 'Analysis complete.');
- if (analysisResult.geoJson) {
+ // Reconstruct standard GeoJSON from flattened schema if present
+ let geoJson: FeatureCollection | null = null;
+ if (analysisResult.geoJson && analysisResult.geoJson.features) {
+ geoJson = {
+ type: 'FeatureCollection',
+ features: analysisResult.geoJson.features.map(f => ({
+ type: 'Feature',
+ geometry: {
+ type: f.geometryType as any,
+ coordinates: f.coordinates as any
+ },
+ properties: {
+ name: f.name,
+ description: f.description
+ }
+ }))
+ };
+ }
+
+ if (geoJson) {
uiStream.append(
);
}
@@ -171,6 +190,7 @@ async function submit(formData?: FormData, skip?: boolean) {
role: 'assistant',
content: JSON.stringify({
...analysisResult,
+ geoJson: geoJson, // Use reconstructed GeoJSON for storage/UI
image: dataUrl,
mapboxImage: mapboxDataUrl,
googleImage: googleDataUrl
diff --git a/lib/agents/resolution-search.tsx b/lib/agents/resolution-search.tsx
index d456fe49..9985712d 100644
--- a/lib/agents/resolution-search.tsx
+++ b/lib/agents/resolution-search.tsx
@@ -1,45 +1,10 @@
import { CoreMessage, streamObject } from 'ai'
import { getModel } from '@/lib/utils'
-import { z } from 'zod'
import { tavily } from '@tavily/core'
+import { resolutionSearchSchema } from '@/lib/schema/resolution-search'
// This agent is now a pure data-processing module, with no UI dependencies.
-// Define the schema for the structured response from the AI.
-const resolutionSearchSchema = z.object({
- summary: z.string().describe('A detailed text summary of the analysis, including land feature classification, points of interest, relevant current news, and temporal context.'),
- geoJson: z.object({
- type: z.literal('FeatureCollection'),
- features: z.array(z.object({
- type: z.literal('Feature'),
- geometry: z.object({
- type: z.string(), // e.g., 'Point', 'Polygon'
- coordinates: z.any(),
- }),
- properties: z.object({
- name: z.string(),
- description: z.string().optional(),
- }),
- })),
- }).describe('A GeoJSON object containing points of interest and classified land features to be overlaid on the map.'),
- extractedCoordinates: z.object({
- latitude: z.number(),
- longitude: z.number()
- }).optional().describe('The extracted geocoordinates of the center of the image.'),
- cogInfo: z.object({
- applicable: z.boolean(),
- description: z.string().optional()
- }).optional().describe('Information about whether Cloud Optimized GeoTIFF (COG) data is applicable or available for this area.'),
- newsContext: z.object({
- hasRecentNews: z.boolean(),
- newsItems: z.array(z.object({
- title: z.string(),
- summary: z.string(),
- relevance: z.string()
- })).optional()
- }).optional().describe('Recent news and events relevant to the analyzed location.')
-})
-
export interface DrawnFeature {
id: string;
type: 'Polygon' | 'LineString';
@@ -162,7 +127,7 @@ Use these user-drawn areas/lines as primary areas of interest for your analysis.
4. **Coordinate Extraction:** If possible, confirm or refine the geocoordinates (latitude/longitude) of the center of the image.
5. **COG Applicability:** Determine if this location would benefit from Cloud Optimized GeoTIFF (COG) analysis for high-precision temporal or spectral data.
6. **News Integration:** Reference any recent news or events that may be relevant to the current state of the location.
-7. **Structured Output:** Return your findings in a structured JSON format including summary, geoJson, and newsContext.
+7. **Structured Output:** Return your findings in a structured JSON format including summary, geoJson (if any), news context, and any extracted coordinates or COG information. Use the provided schema.
Your analysis should be based on the visual information in the image, the temporal context provided, and your general knowledge. Do not attempt to access external websites or perform web searches beyond what has been provided.
diff --git a/lib/schema/resolution-search.ts b/lib/schema/resolution-search.ts
new file mode 100644
index 00000000..e7908abd
--- /dev/null
+++ b/lib/schema/resolution-search.ts
@@ -0,0 +1,41 @@
+import { z } from 'zod'
+
+// This schema is designed to be compatible with xAI's OpenAI-compatible endpoint.
+// We use a flattened structure and avoid z.literal (which generates JSON Schema 'const')
+// and z.any() to ensure maximum compatibility with xAI's schema validation.
+// This follows the pattern established in lib/schema/geospatial.tsx.
+
+export const resolutionSearchSchema = z.object({
+ summary: z.string().describe('A detailed text summary of the analysis, including land feature classification, points of interest, relevant current news, and temporal context.'),
+
+ // geoJson is optional so the model is not forced to produce features when none are found.
+ geoJson: z.object({
+ type: z.string().describe("Must be 'FeatureCollection'"),
+ features: z.array(z.object({
+ type: z.string().describe("Must be 'Feature'"),
+ geometryType: z.string().describe("The type of geometry, e.g., 'Point', 'Polygon'"),
+ coordinates: z.array(z.number())
+ .or(z.array(z.array(z.number())))
+ .or(z.array(z.array(z.array(z.number()))))
+ .describe('Coordinates for the geometry'),
+ name: z.string().describe('Name of the feature or point of interest'),
+ description: z.string().optional().describe('Description of the feature')
+ }))
+ }).optional().describe('A collection of points of interest and classified land features to be overlaid on the map.'),
+
+ // Flattened top-level fields for better xAI compatibility
+ extractedLatitude: z.number().optional().describe('The extracted latitude of the center of the image.'),
+ extractedLongitude: z.number().optional().describe('The extracted longitude of the center of the image.'),
+
+ cogApplicable: z.boolean().optional().describe('Whether Cloud Optimized GeoTIFF (COG) data is applicable for this area.'),
+ cogDescription: z.string().optional().describe('Description of COG data availability or benefits.'),
+
+ hasRecentNews: z.boolean().optional().describe('Whether there is recent news relevant to the location.'),
+ newsItems: z.array(z.object({
+ title: z.string(),
+ summary: z.string(),
+ relevance: z.string()
+ })).optional().describe('List of recent news items relevant to the location.')
+})
+
+export type ResolutionSearch = z.infer;