mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
Added serper web search tool/block
This commit is contained in:
100
blocks/blocks/serper.ts
Normal file
100
blocks/blocks/serper.ts
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
150
tools/serper/search.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user