mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
Added tavily search/extract blocks/tools
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { BlockConfig } from '../types'
|
||||
import { SearchIcon } from '@/components/icons'
|
||||
import { SerperIcon } from '@/components/icons'
|
||||
import { SearchResponse } from '@/tools/serper/search'
|
||||
|
||||
export const SerperBlock: BlockConfig<SearchResponse> = {
|
||||
@@ -8,7 +8,7 @@ export const SerperBlock: BlockConfig<SearchResponse> = {
|
||||
title: 'Web Search',
|
||||
description: 'Search the web',
|
||||
bgColor: '#4285F4', // Google blue
|
||||
icon: SearchIcon,
|
||||
icon: SerperIcon,
|
||||
category: 'tools',
|
||||
},
|
||||
tools: {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { BlockConfig } from '../types'
|
||||
import { TavilyIcon } from '@/components/icons'
|
||||
import { SearchResponse } from '@/tools/tavily/search'
|
||||
import { ExtractResponse } from '@/tools/tavily/extract'
|
||||
|
||||
export const TavilySearchBlock: BlockConfig<SearchResponse> = {
|
||||
type: 'tavily_search',
|
||||
toolbar: {
|
||||
title: 'Tavily Search',
|
||||
description: 'Search the web using Tavily AI',
|
||||
bgColor: '#0066FF',
|
||||
icon: TavilyIcon,
|
||||
category: 'tools',
|
||||
},
|
||||
tools: {
|
||||
access: ['tavily_search']
|
||||
},
|
||||
workflow: {
|
||||
inputs: {
|
||||
query: { type: 'string', required: true },
|
||||
apiKey: { type: 'string', required: true },
|
||||
max_results: { type: 'number', required: false }
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
type: {
|
||||
query: 'string',
|
||||
results: 'json',
|
||||
response_time: 'number'
|
||||
}
|
||||
}
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter your search query...'
|
||||
},
|
||||
{
|
||||
id: 'max_results',
|
||||
title: 'Max Results',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: ['5', '10', '15', '20']
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter your Tavily API key',
|
||||
password: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export const TavilyExtractBlock: BlockConfig<ExtractResponse> = {
|
||||
type: 'tavily_extract',
|
||||
toolbar: {
|
||||
title: 'Tavily Extract',
|
||||
description: 'Scrape website content',
|
||||
bgColor: '#0066FF',
|
||||
icon: TavilyIcon,
|
||||
category: 'tools',
|
||||
},
|
||||
tools: {
|
||||
access: ['tavily_extract']
|
||||
},
|
||||
workflow: {
|
||||
inputs: {
|
||||
urls: { type: 'string', required: true },
|
||||
apiKey: { type: 'string', required: true },
|
||||
extract_depth: { type: 'string', required: false }
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
type: {
|
||||
results: 'json',
|
||||
failed_results: 'any',
|
||||
response_time: 'number'
|
||||
}
|
||||
}
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'urls',
|
||||
title: 'URL',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter URL to extract content from...'
|
||||
},
|
||||
{
|
||||
id: 'extract_depth',
|
||||
title: 'Extract Depth',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: ['basic', 'advanced']
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter your Tavily API key',
|
||||
password: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -12,6 +12,7 @@ import { SlackMessageBlock } from './blocks/slack'
|
||||
import { GitHubBlock } from './blocks/github'
|
||||
import { ConditionBlock } from './blocks/condition'
|
||||
import { SerperBlock } from './blocks/serper'
|
||||
import { TavilySearchBlock, TavilyExtractBlock } from './blocks/tavily'
|
||||
|
||||
// Export blocks for ease of use
|
||||
export {
|
||||
@@ -25,7 +26,9 @@ export {
|
||||
SlackMessageBlock,
|
||||
GitHubBlock,
|
||||
ConditionBlock,
|
||||
SerperBlock
|
||||
SerperBlock,
|
||||
TavilySearchBlock,
|
||||
TavilyExtractBlock
|
||||
}
|
||||
|
||||
// Registry of all block configurations
|
||||
@@ -40,7 +43,9 @@ const blocks: Record<string, BlockConfig> = {
|
||||
slack_message: SlackMessageBlock,
|
||||
github_repo_info: GitHubBlock,
|
||||
condition: ConditionBlock,
|
||||
serper_search: SerperBlock
|
||||
serper_search: SerperBlock,
|
||||
tavily_search: TavilySearchBlock,
|
||||
tavily_extract: TavilyExtractBlock
|
||||
}
|
||||
|
||||
// Build a reverse mapping of tools to block types
|
||||
|
||||
@@ -1016,3 +1016,17 @@ export const GithubIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const SerperIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12zm0-22.5c5.802 0 10.5 4.698 10.5 10.5s-4.698 10.5-10.5 10.5S1.5 17.802 1.5 12 6.198 1.5 12 1.5z" fill="currentColor" />
|
||||
<path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12zm0-22.5c5.802 0 10.5 4.698 10.5 10.5s-4.698 10.5-10.5 10.5S1.5 17.802 1.5 12 6.198 1.5 12 1.5z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TavilyIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12zm0-22.5c5.802 0 10.5 4.698 10.5 10.5s-4.698 10.5-10.5 10.5S1.5 17.802 1.5 12 6.198 1.5 12 1.5z" fill="currentColor" />
|
||||
<path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12zm0-22.5c5.802 0 10.5 4.698 10.5 10.5s-4.698 10.5-10.5 10.5S1.5 17.802 1.5 12 6.198 1.5 12 1.5z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
+5
-1
@@ -15,6 +15,8 @@ import { readUrlTool } from './jina/reader'
|
||||
import { slackMessageTool } from './slack/message'
|
||||
import { repoInfoTool } from './github/repo'
|
||||
import { searchTool as serperSearch } from './serper/search'
|
||||
import { searchTool as tavilySearch } from './tavily/search'
|
||||
import { extractTool as tavilyExtract } from './tavily/extract'
|
||||
|
||||
// Registry of all available tools
|
||||
export const tools: Record<string, ToolConfig> = {
|
||||
@@ -43,7 +45,9 @@ export const tools: Record<string, ToolConfig> = {
|
||||
// GitHub Tools
|
||||
'github_repoinfo': repoInfoTool,
|
||||
// Search Tools
|
||||
'serper_search': serperSearch
|
||||
'serper_search': serperSearch,
|
||||
'tavily_search': tavilySearch,
|
||||
'tavily_extract': tavilyExtract
|
||||
}
|
||||
|
||||
// Get a tool by its ID
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { ToolConfig, ToolResponse } from '../types'
|
||||
|
||||
interface ExtractParams {
|
||||
urls: string | string[]
|
||||
apiKey: string
|
||||
extract_depth?: 'basic' | 'advanced'
|
||||
}
|
||||
|
||||
interface ExtractResult {
|
||||
url: string
|
||||
raw_content: string
|
||||
}
|
||||
|
||||
export interface ExtractResponse extends ToolResponse {
|
||||
output: {
|
||||
results: ExtractResult[]
|
||||
failed_results?: Array<{
|
||||
url: string
|
||||
error: string
|
||||
}>
|
||||
response_time: number
|
||||
}
|
||||
}
|
||||
|
||||
export const extractTool: ToolConfig<ExtractParams, ExtractResponse> = {
|
||||
id: 'tavily.extract',
|
||||
name: 'Tavily Extract',
|
||||
description: 'Extract web page content from URLs using Tavily Extract',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
urls: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'URL or array of URLs to extract content from'
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Tavily API Key',
|
||||
requiredForToolCall: true
|
||||
},
|
||||
extract_depth: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'The depth of extraction (basic=1 credit/5 URLs, advanced=2 credits/5 URLs)'
|
||||
}
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.tavily.com/extract',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Authorization': `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
urls: typeof params.urls === 'string' ? [params.urls] : params.urls
|
||||
}
|
||||
|
||||
if (params.extract_depth) body.extract_depth = params.extract_depth
|
||||
|
||||
return body
|
||||
}
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || 'Failed to extract content')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results: data.results || [],
|
||||
...(data.failed_results && { failed_results: data.failed_results }),
|
||||
response_time: data.response_time
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
: 'An error occurred while extracting content'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { ToolConfig, ToolResponse } from '../types'
|
||||
|
||||
interface SearchParams {
|
||||
query: string
|
||||
apiKey: string
|
||||
max_results?: number
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
title: string
|
||||
url: string
|
||||
snippet: string
|
||||
raw_content?: string
|
||||
}
|
||||
|
||||
export interface SearchResponse extends ToolResponse {
|
||||
output: {
|
||||
query: string
|
||||
results: SearchResult[]
|
||||
response_time: number
|
||||
}
|
||||
}
|
||||
|
||||
export const searchTool: ToolConfig<SearchParams, SearchResponse> = {
|
||||
id: 'tavily_search',
|
||||
name: 'Tavily Search',
|
||||
description: 'Search the web using Tavily AI Search API',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The search query to execute'
|
||||
},
|
||||
max_results: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum number of results (1-20)'
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description: 'Tavily API Key'
|
||||
}
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.tavily.com/search',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Authorization': `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
query: params.query
|
||||
}
|
||||
|
||||
// Only include optional parameters if explicitly set
|
||||
if (params.max_results) body.max_results = params.max_results
|
||||
|
||||
return body
|
||||
}
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || 'Failed to perform search')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
query: data.query,
|
||||
results: data.results.map((result: any) => ({
|
||||
title: result.title,
|
||||
url: result.url,
|
||||
snippet: result.snippet,
|
||||
...(result.raw_content && { raw_content: result.raw_content })
|
||||
})),
|
||||
response_time: data.response_time
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
: 'An error occurred while performing the search'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user