refactor(WebSearchTool): remove inline image fetching, add search_images param
Browse files- Remove automatic image fetching and inline embedding from WebSearchTool
- Add optional search_images parameter to query image-specific results
- Support SEARXNG_BASE_URL env var for SearXNG endpoint configuration
- Remove Output.images field and related inlineImageUtils import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
src/tools/WebSearchTool/WebSearchTool.ts
CHANGED
|
@@ -11,11 +11,13 @@ import {
|
|
| 11 |
renderToolUseMessage,
|
| 12 |
renderToolUseProgressMessage,
|
| 13 |
} from './UI.js'
|
| 14 |
-
import { fetchImagesAsInline } from '../../utils/inlineImageUtils.js'
|
| 15 |
-
|
| 16 |
const inputSchema = lazySchema(() =>
|
| 17 |
z.strictObject({
|
| 18 |
query: z.string().min(2).describe('The search query to use'),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
}),
|
| 20 |
)
|
| 21 |
|
|
@@ -45,9 +47,7 @@ const outputSchema = lazySchema(() =>
|
|
| 45 |
}),
|
| 46 |
)
|
| 47 |
|
| 48 |
-
export type Output = z.infer<ReturnType<typeof outputSchema>>
|
| 49 |
-
images?: Array<{ url: string; base64: string; mediaType: string }>
|
| 50 |
-
}
|
| 51 |
|
| 52 |
export type { WebSearchProgress } from '../../types/tools.js'
|
| 53 |
import type { WebSearchProgress } from '../../types/tools.js'
|
|
@@ -56,12 +56,17 @@ import type { WebSearchProgress } from '../../types/tools.js'
|
|
| 56 |
* 使用 SearXNG 本地搜索
|
| 57 |
*/
|
| 58 |
async function searchSearXNG(
|
| 59 |
-
query: string
|
|
|
|
| 60 |
): Promise<Array<{ title: string; url: string; snippet?: string; image?: string }>> {
|
| 61 |
try {
|
| 62 |
-
const
|
|
|
|
| 63 |
url.searchParams.set('q', query)
|
| 64 |
url.searchParams.set('format', 'json')
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
const controller = new AbortController()
|
| 67 |
const timeout = setTimeout(() => controller.abort(), 10000)
|
|
@@ -243,7 +248,7 @@ export const WebSearchTool = buildTool({
|
|
| 243 |
const useTavily = !!process.env.TAVILY_API_KEY
|
| 244 |
const results = useTavily
|
| 245 |
? await searchTavily(input.query)
|
| 246 |
-
: await searchSearXNG(input.query)
|
| 247 |
|
| 248 |
const cleaned = results.map(r => ({
|
| 249 |
...r,
|
|
@@ -260,14 +265,6 @@ export const WebSearchTool = buildTool({
|
|
| 260 |
},
|
| 261 |
]
|
| 262 |
|
| 263 |
-
const imageUrls = cleaned
|
| 264 |
-
.map((r) => r.image)
|
| 265 |
-
.filter((url): url is string => !!url)
|
| 266 |
-
.slice(0, 4)
|
| 267 |
-
|
| 268 |
-
const images =
|
| 269 |
-
imageUrls.length > 0 ? await fetchImagesAsInline(imageUrls, 4) : undefined
|
| 270 |
-
|
| 271 |
const duration = (performance.now() - start) / 1000
|
| 272 |
|
| 273 |
return {
|
|
@@ -275,7 +272,6 @@ export const WebSearchTool = buildTool({
|
|
| 275 |
query: input.query,
|
| 276 |
results: output,
|
| 277 |
durationSeconds: duration,
|
| 278 |
-
images,
|
| 279 |
},
|
| 280 |
}
|
| 281 |
} catch (error) {
|
|
@@ -318,17 +314,7 @@ export const WebSearchTool = buildTool({
|
|
| 318 |
return {
|
| 319 |
tool_use_id: toolUseID,
|
| 320 |
type: 'tool_result',
|
| 321 |
-
content: [
|
| 322 |
-
{ type: 'text', text },
|
| 323 |
-
...(output.images?.map((img) => ({
|
| 324 |
-
type: 'image' as const,
|
| 325 |
-
source: {
|
| 326 |
-
type: 'base64' as const,
|
| 327 |
-
data: img.base64,
|
| 328 |
-
media_type: img.mediaType as 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp',
|
| 329 |
-
},
|
| 330 |
-
})) ?? []),
|
| 331 |
-
],
|
| 332 |
}
|
| 333 |
},
|
| 334 |
}) satisfies ToolDef<any, Output, WebSearchProgress>
|
|
|
|
| 11 |
renderToolUseMessage,
|
| 12 |
renderToolUseProgressMessage,
|
| 13 |
} from './UI.js'
|
|
|
|
|
|
|
| 14 |
const inputSchema = lazySchema(() =>
|
| 15 |
z.strictObject({
|
| 16 |
query: z.string().min(2).describe('The search query to use'),
|
| 17 |
+
search_images: z.boolean().optional().describe(
|
| 18 |
+
'Set to true to search for images specifically (vs general web results). ' +
|
| 19 |
+
'Use when the user asks to see/look up photos, images, or visual references.'
|
| 20 |
+
),
|
| 21 |
}),
|
| 22 |
)
|
| 23 |
|
|
|
|
| 47 |
}),
|
| 48 |
)
|
| 49 |
|
| 50 |
+
export type Output = z.infer<ReturnType<typeof outputSchema>>
|
|
|
|
|
|
|
| 51 |
|
| 52 |
export type { WebSearchProgress } from '../../types/tools.js'
|
| 53 |
import type { WebSearchProgress } from '../../types/tools.js'
|
|
|
|
| 56 |
* 使用 SearXNG 本地搜索
|
| 57 |
*/
|
| 58 |
async function searchSearXNG(
|
| 59 |
+
query: string,
|
| 60 |
+
searchImages?: boolean
|
| 61 |
): Promise<Array<{ title: string; url: string; snippet?: string; image?: string }>> {
|
| 62 |
try {
|
| 63 |
+
const baseUrl = process.env.SEARXNG_BASE_URL || 'http://localhost:8080'
|
| 64 |
+
const url = new URL('/search', baseUrl)
|
| 65 |
url.searchParams.set('q', query)
|
| 66 |
url.searchParams.set('format', 'json')
|
| 67 |
+
if (searchImages) {
|
| 68 |
+
url.searchParams.set('categories', 'images')
|
| 69 |
+
}
|
| 70 |
|
| 71 |
const controller = new AbortController()
|
| 72 |
const timeout = setTimeout(() => controller.abort(), 10000)
|
|
|
|
| 248 |
const useTavily = !!process.env.TAVILY_API_KEY
|
| 249 |
const results = useTavily
|
| 250 |
? await searchTavily(input.query)
|
| 251 |
+
: await searchSearXNG(input.query, input.search_images)
|
| 252 |
|
| 253 |
const cleaned = results.map(r => ({
|
| 254 |
...r,
|
|
|
|
| 265 |
},
|
| 266 |
]
|
| 267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
const duration = (performance.now() - start) / 1000
|
| 269 |
|
| 270 |
return {
|
|
|
|
| 272 |
query: input.query,
|
| 273 |
results: output,
|
| 274 |
durationSeconds: duration,
|
|
|
|
| 275 |
},
|
| 276 |
}
|
| 277 |
} catch (error) {
|
|
|
|
| 314 |
return {
|
| 315 |
tool_use_id: toolUseID,
|
| 316 |
type: 'tool_result',
|
| 317 |
+
content: [{ type: 'text', text }],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
}
|
| 319 |
},
|
| 320 |
}) satisfies ToolDef<any, Output, WebSearchProgress>
|