-
-
Notifications
You must be signed in to change notification settings - Fork 7
Enhance Geospatial Tool Usage #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
|
|
||
| import { StreamableValue } from 'ai/rsc' | ||
| import { Card } from '@/components/ui/card' | ||
|
|
||
| interface Image { | ||
| imageUrl: string | ||
| link: string | ||
| title: string | ||
| } | ||
|
|
||
| export const ImageSearchSection = ({ result }: { result: StreamableValue<string> }) => { | ||
| const data = JSON.parse(result.value) as { images: Image[] } | ||
|
|
||
| return ( | ||
| <Card className="p-4 mt-2"> | ||
| <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4"> | ||
| {data.images?.map((image, index) => ( | ||
| <a key={index} href={image.link} target="_blank" rel="noopener noreferrer"> | ||
| <img | ||
| src={image.imageUrl} | ||
| alt={image.title} | ||
| className="object-cover w-full h-full rounded-lg" | ||
| /> | ||
| </a> | ||
| ))} | ||
| </div> | ||
| </Card> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,12 @@ import { getTools } from './tools' | |
| import { getModel } from '../utils' | ||
|
|
||
| export async function researcher( | ||
| dynamicSystemPrompt: string, // New parameter | ||
| dynamicSystemPrompt: string, | ||
| uiStream: ReturnType<typeof createStreamableUI>, | ||
| streamText: ReturnType<typeof createStreamableValue<string>>, | ||
| messages: CoreMessage[], | ||
| // mcp: any, // Removed mcp parameter | ||
| useSpecificModel?: boolean | ||
| useSpecificModel?: boolean, | ||
| category?: 'geospatial' | 'web_search' | 'general' | ||
| ) { | ||
| let fullResponse = '' | ||
| let hasError = false | ||
|
|
@@ -43,6 +43,7 @@ Tool Usage Guide: | |
|
|
||
| - For general web searches for factual information: Use the 'search' tool. | ||
| - For retrieving content from specific URLs provided by the user: Use the 'retrieve' tool. (Do not use this for URLs found in search results). | ||
| - For image searches: Use the 'imageSearch' tool. This is especially useful for visual queries. | ||
|
|
||
| - For any questions involving locations, places, addresses, geographical features, finding businesses or points of interest, distances between locations, or directions: You MUST use the 'geospatialQueryTool'. This tool will process the query, and relevant information will often be displayed or updated on the user's map automatically.** | ||
| Examples of queries for 'geospatialQueryTool': | ||
|
|
@@ -65,9 +66,14 @@ Analysis & Planning | |
|
|
||
| When you use 'geospatialQueryTool', you don't need to describe how the map will change; simply provide your textual answer based on the query, and trust the map will update appropriately. | ||
| `; | ||
| const geospatial_prompt = `The user's query has been identified as geospatial. | ||
| You MUST use the 'geospatialQueryTool' to answer this question. | ||
| Do not use any other tools. If the query cannot be answered with the geospatial tool, respond that you are unable to answer.`; | ||
|
|
||
| const systemToUse = dynamicSystemPrompt && dynamicSystemPrompt.trim() !== '' ? dynamicSystemPrompt : default_system_prompt; | ||
|
|
||
| let systemToUse = dynamicSystemPrompt && dynamicSystemPrompt.trim() !== '' ? dynamicSystemPrompt : default_system_prompt; | ||
| if (category === 'geospatial') { | ||
| systemToUse = `${systemToUse}\n\n${geospatial_prompt}`; | ||
| } | ||
|
Comment on lines
+69
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The geospatial directive is overly restrictive: "Do not use any other tools" and "respond that you are unable to answer" if the geospatial tool can’t handle it. This can degrade UX for mixed queries that are primarily geospatial but still require supplemental facts (e.g., POI details) from web search. Prefer a "prioritize geospatial" policy with graceful fallback. SuggestionRelax the directive to allow fallback when needed while still prioritizing the geospatial tool: const geospatial_prompt = `The user's query has been identified as geospatial.
You MUST prioritize using the 'geospatialQueryTool' to answer this question first.
If the geospatial tool is insufficient for a subtask (e.g., factual details or current info), you MAY use other available tools to complete the request.
When you use 'geospatialQueryTool', do not describe how the map will change; provide the textual answer and trust the map will update appropriately.`;Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change. |
||
| const result = await nonexperimental_streamText({ | ||
| model: getModel() as LanguageModel, | ||
| maxTokens: 2500, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
|
|
||
| import { createStreamableValue } from 'ai/rsc' | ||
| import { searchSchema } from '@/lib/schema/search' | ||
| import { Card } from '@/components/ui/card' | ||
| import { ImageSearchSection } from '@/components/image-search-section' | ||
| import { ToolProps } from '.' | ||
|
|
||
| export const imageSearchTool = ({ uiStream, fullResponse }: ToolProps) => ({ | ||
| description: 'Search the web for images', | ||
| parameters: searchSchema, | ||
| execute: async ({ | ||
| query, | ||
| max_results, | ||
| }: { | ||
| query: string | ||
| max_results: number | ||
| }) => { | ||
| let hasError = false | ||
| const streamResults = createStreamableValue<string>() | ||
| uiStream.append(<ImageSearchSection result={streamResults.value} />) | ||
|
|
||
| const filledQuery = | ||
| query.length < 5 ? query + ' '.repeat(5 - query.length) : query | ||
| let searchResult | ||
| try { | ||
| searchResult = await serperImageSearch(filledQuery, max_results) | ||
| } catch (error) { | ||
| console.error('Image search API error:', error) | ||
| hasError = true | ||
| } | ||
|
|
||
| if (hasError) { | ||
| fullResponse += `\nAn error occurred while searching for images of "${query}".` | ||
| uiStream.update( | ||
| <Card className="p-4 mt-2 text-sm"> | ||
| {`An error occurred while searching for images of "${query}".`} | ||
| </Card> | ||
| ) | ||
| return searchResult | ||
| } | ||
|
|
||
| streamResults.done(JSON.stringify(searchResult)) | ||
|
|
||
| return searchResult | ||
| } | ||
| }) | ||
|
|
||
| async function serperImageSearch( | ||
| query: string, | ||
| maxResults: number = 10, | ||
| ): Promise<any> { | ||
| const apiKey = process.env.SERPER_API_KEY | ||
| const response = await fetch('https://google.serper.dev/images', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'X-API-KEY': apiKey!, | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify({ | ||
| q: query, | ||
| num: maxResults, | ||
| }) | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`Error: ${response.status}`) | ||
| } | ||
|
|
||
| const data = await response.json() | ||
| return data | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,8 @@ import { DeepPartial } from 'ai' | |
| import { z } from 'zod' | ||
|
|
||
| export const nextActionSchema = z.object({ | ||
| next: z.enum(['inquire', 'proceed']) // "generate_ui" | ||
| next: z.enum(['inquire', 'proceed']), // "generate_ui" | ||
| category: z.enum(['geospatial', 'web_search', 'general']).optional() | ||
|
Comment on lines
+5
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You instruct the model to classify every query, but the schema makes SuggestionMake export const nextActionSchema = z.object({
next: z.enum(['inquire', 'proceed']),
category: z.enum(['geospatial', 'web_search', 'general'])
});Note: This will require ensuring all Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change. |
||
| }) | ||
|
|
||
| export type NextAction = DeepPartial<typeof nextActionSchema> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actionis typed asany, which weakens guarantees right where critical control-flow fields (next,category) are introduced. This makes it easier for unintended shapes to slip through and complicates future refactors.Suggestion
Introduce a minimal local type for the shape you rely on and avoid
any:(Alternatively, derive the type from your Zod schema and use that.)
Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.