Added serper web search tool/block

This commit is contained in:
Waleed Latif
2025-02-04 19:45:50 -08:00
parent e75dca43a3
commit 3bcf1fedd4
4 changed files with 259 additions and 3 deletions

100
blocks/blocks/serper.ts Normal file
View File

@@ -0,0 +1,100 @@
import { BlockConfig } from '../types'
import { SearchIcon } from '@/components/icons'
import { SearchResponse } from '@/tools/serper/search'
export const SerperBlock: BlockConfig<SearchResponse> = {
type: 'serper_search',
toolbar: {
title: 'Web Search',
description: 'Search the web',
bgColor: '#4285F4', // Google blue
icon: SearchIcon,
category: 'tools',
},
tools: {
access: ['serper_search']
},
workflow: {
inputs: {
query: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
num: { type: 'number', required: false },
gl: { type: 'string', required: false },
hl: { type: 'string', required: false },
type: { type: 'string', required: false }
},
outputs: {
response: {
type: {
searchResults: 'json'
}
}
},
subBlocks: [
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your search query...'
},
{
id: 'type',
title: 'Search Type',
type: 'dropdown',
layout: 'half',
options: ['search', 'news', 'places', 'images']
},
{
id: 'num',
title: 'Number of Results',
type: 'dropdown',
layout: 'half',
options: ['10', '20', '30', '40', '50', '100']
},
{
id: 'gl',
title: 'Country',
type: 'dropdown',
layout: 'half',
options: [
'US',
'GB',
'CA',
'AU',
'DE',
'FR',
'ES',
'IT',
'JP',
'KR'
]
},
{
id: 'hl',
title: 'Language',
type: 'dropdown',
layout: 'half',
options: [
'en',
'es',
'fr',
'de',
'it',
'pt',
'ja',
'ko',
'zh'
]
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Serper API key',
password: true
}
]
}
}

View File

@@ -11,6 +11,7 @@ import { TranslateBlock } from './blocks/translate'
import { SlackMessageBlock } from './blocks/slack'
import { GitHubBlock } from './blocks/github'
import { ConditionBlock } from './blocks/condition'
import { SerperBlock } from './blocks/serper'
// Export blocks for ease of use
export {
@@ -23,7 +24,8 @@ export {
TranslateBlock,
SlackMessageBlock,
GitHubBlock,
ConditionBlock
ConditionBlock,
SerperBlock
}
// Registry of all block configurations
@@ -37,7 +39,8 @@ const blocks: Record<string, BlockConfig> = {
translate: TranslateBlock,
slack_message: SlackMessageBlock,
github_repo_info: GitHubBlock,
condition: ConditionBlock
condition: ConditionBlock,
serper_search: SerperBlock
}
// Build a reverse mapping of tools to block types

View File

@@ -14,6 +14,7 @@ import { scrapeTool } from './firecrawl/scrape'
import { readUrlTool } from './jina/reader'
import { slackMessageTool } from './slack/message'
import { repoInfoTool } from './github/repo'
import { searchTool as serperSearch } from './serper/search'
// Registry of all available tools
export const tools: Record<string, ToolConfig> = {
@@ -40,7 +41,9 @@ export const tools: Record<string, ToolConfig> = {
// Slack Tools
'slack_message': slackMessageTool,
// GitHub Tools
'github_repoinfo': repoInfoTool
'github_repoinfo': repoInfoTool,
// Search Tools
'serper_search': serperSearch
}
// Get a tool by its ID

150
tools/serper/search.ts Normal file
View File

@@ -0,0 +1,150 @@
import { ToolConfig, ToolResponse } from '../types'
interface SearchParams {
query: string
apiKey: string
num?: number
gl?: string // country code
hl?: string // language code
type?: 'search' | 'news' | 'places' | 'images'
}
export interface SearchResult {
title: string
link: string
snippet: string
position: number
imageUrl?: string
date?: string
rating?: string
reviews?: string
address?: string
}
export interface SearchResponse extends ToolResponse {
output: {
searchResults: SearchResult[]
}
}
export const searchTool: ToolConfig<SearchParams, SearchResponse> = {
id: 'serper_search',
name: 'Web Search',
description: 'Search the web using Serper.dev API',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
description: 'The search query'
},
apiKey: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'Serper API Key'
},
num: {
type: 'number',
required: false,
description: 'Number of results to return'
},
gl: {
type: 'string',
required: false,
description: 'Country code for search results'
},
hl: {
type: 'string',
required: false,
description: 'Language code for search results'
},
type: {
type: 'string',
required: false,
description: 'Type of search to perform'
}
},
request: {
url: (params) => `https://google.serper.dev/${params.type || 'search'}`,
method: 'POST',
headers: (params) => ({
'X-API-KEY': params.apiKey,
'Content-Type': 'application/json'
}),
body: (params) => {
const body: Record<string, any> = {
q: params.query
}
// Only include optional parameters if they are explicitly set
if (params.num) body.num = params.num
if (params.gl) body.gl = params.gl
if (params.hl) body.hl = params.hl
return body
}
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || 'Failed to perform search')
}
const searchType = response.url.split('/').pop() || 'search'
let searchResults: SearchResult[] = []
if (searchType === 'news') {
searchResults = data.news?.map((item: any, index: number) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
position: index + 1,
date: item.date,
imageUrl: item.imageUrl
})) || []
} else if (searchType === 'places') {
searchResults = data.places?.map((item: any, index: number) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
position: index + 1,
rating: item.rating,
reviews: item.reviews,
address: item.address
})) || []
} else if (searchType === 'images') {
searchResults = data.images?.map((item: any, index: number) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
position: index + 1,
imageUrl: item.imageUrl
})) || []
} else {
searchResults = data.organic?.map((item: any, index: number) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
position: index + 1
})) || []
}
return {
success: true,
output: {
searchResults
}
}
},
transformError: (error) => {
return error instanceof Error
? error.message
: 'An error occurred while performing the search'
}
}