From db9e91152d2ef716d6955f73a034cc8ec9611368 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 23 Jun 2025 09:51:09 +0800 Subject: [PATCH] Feat: Add Tavily operator #3221 (#8400) ### What problem does this PR solve? Feat: Add Tavily operator #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- .../node/dropdown/next-step-dropdown.tsx | 2 +- web/src/pages/agent/constant.tsx | 38 +++++++++++- .../agent/form-sheet/use-form-config-map.tsx | 6 ++ .../agent/form/agent-form/agent-tools.tsx | 2 +- web/src/pages/agent/form/agent-form/index.tsx | 1 + .../pages/agent/form/tavily-form/index.tsx | 26 ++++---- .../agent/form/tavily-form/use-values.ts | 58 +++--------------- .../form/tavily-form/use-watch-change.ts | 38 ++++-------- .../pages/agent/form/tool-form/constant.ts | 2 +- .../form/tool-form/tavily-form/index.tsx | 60 +++++++++++++++++++ .../pages/agent/form/tool-form/use-values.ts | 43 +++++++++++++ .../agent/form/tool-form/use-watch-change.ts | 39 ++++++++++++ web/src/pages/agent/hooks.tsx | 2 + web/src/pages/agent/hooks/use-add-node.ts | 2 + 14 files changed, 226 insertions(+), 93 deletions(-) create mode 100644 web/src/pages/agent/form/tool-form/tavily-form/index.tsx create mode 100644 web/src/pages/agent/form/tool-form/use-values.ts create mode 100644 web/src/pages/agent/form/tool-form/use-watch-change.ts diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx index e741286f2..f8ffc4cb3 100644 --- a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx +++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx @@ -88,7 +88,7 @@ function AccordionOperators() { Tools - + diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index 3f1d192e1..cebd46240 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -387,7 +387,7 @@ const initialQueryBaseValues = { }; export const initialRetrievalValues = { - query: '', + query: AgentGlobals.SysQuery, top_n: 8, top_k: 1024, kb_ids: [], @@ -686,6 +686,41 @@ export const initialAgentValues = { }, }; +export enum TavilySearchDepth { + Basic = 'basic', + Advanced = 'advanced', +} + +export enum TavilyTopic { + News = 'news', + General = 'general', +} + +export const initialTavilyValues = { + api_key: '', + query: AgentGlobals.SysQuery, + search_depth: TavilySearchDepth.Basic, + topic: TavilyTopic.General, + max_results: 5, + days: 7, + include_answer: false, + include_raw_content: true, + include_images: false, + include_image_descriptions: false, + include_domains: [], + exclude_domains: [], + outputs: { + formalized_content: { + value: '', + type: 'string', + }, + json: { + value: {}, + type: 'Object', + }, + }, +}; + export const CategorizeAnchorPointPositions = [ { top: 1, right: 34 }, { top: 8, right: 18 }, @@ -813,6 +848,7 @@ export const NodeMap = { [Operator.WaitingDialogue]: 'ragNode', [Operator.Agent]: 'agentNode', [Operator.Tool]: 'toolNode', + [Operator.Tavily]: 'ragNode', }; export const LanguageOptions = [ diff --git a/web/src/pages/agent/form-sheet/use-form-config-map.tsx b/web/src/pages/agent/form-sheet/use-form-config-map.tsx index d887bf58c..50b537a07 100644 --- a/web/src/pages/agent/form-sheet/use-form-config-map.tsx +++ b/web/src/pages/agent/form-sheet/use-form-config-map.tsx @@ -33,6 +33,7 @@ import RelevantForm from '../form/relevant-form'; import RetrievalForm from '../form/retrieval-form/next'; import RewriteQuestionForm from '../form/rewrite-question-form'; import SwitchForm from '../form/switch-form'; +import TavilyForm from '../form/tavily-form'; import TemplateForm from '../form/template-form'; import ToolForm from '../form/tool-form'; import TuShareForm from '../form/tushare-form'; @@ -375,6 +376,11 @@ export function useFormConfigMap() { defaultValues: {}, schema: z.object({}), }, + [Operator.Tavily]: { + component: TavilyForm, + defaultValues: {}, + schema: z.object({}), + }, }; return FormConfigMap; diff --git a/web/src/pages/agent/form/agent-form/agent-tools.tsx b/web/src/pages/agent/form/agent-form/agent-tools.tsx index 25bbead6d..5961c8e5a 100644 --- a/web/src/pages/agent/form/agent-form/agent-tools.tsx +++ b/web/src/pages/agent/form/agent-form/agent-tools.tsx @@ -36,7 +36,7 @@ export function AgentTools() { {x}
- + { name={`prompts`} render={({ field }) => ( + User Prompt
diff --git a/web/src/pages/agent/form/tavily-form/index.tsx b/web/src/pages/agent/form/tavily-form/index.tsx index 3ff58b37f..461d00910 100644 --- a/web/src/pages/agent/form/tavily-form/index.tsx +++ b/web/src/pages/agent/form/tavily-form/index.tsx @@ -15,20 +15,26 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useMemo } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +import { + TavilySearchDepth, + TavilyTopic, + initialTavilyValues, +} from '../../constant'; +import { INextOperatorForm } from '../../interface'; import { Output, OutputType } from '../components/output'; import { QueryVariable } from '../components/query-variable'; import { DynamicDomain } from './dynamic-domain'; -import { SearchDepth, Topic, defaultValues, useValues } from './use-values'; +import { useValues } from './use-values'; import { useWatchFormChange } from './use-watch-change'; -const TavilyForm = () => { - const values = useValues(); +const TavilyForm = ({ node }: INextOperatorForm) => { + const values = useValues(node); const FormSchema = z.object({ api_key: z.string(), query: z.string(), - search_depth: z.enum([SearchDepth.Advanced, SearchDepth.Basic]), - topic: z.enum([Topic.News, Topic.General]), + search_depth: z.enum([TavilySearchDepth.Advanced, TavilySearchDepth.Basic]), + topic: z.enum([TavilyTopic.News, TavilyTopic.General]), max_results: z.coerce.number(), days: z.coerce.number(), include_answer: z.boolean(), @@ -45,7 +51,7 @@ const TavilyForm = () => { }); const outputList = useMemo(() => { - return Object.entries(defaultValues.outputs).reduce( + return Object.entries(initialTavilyValues.outputs).reduce( (pre, [key, val]) => { pre.push({ title: key, type: val.type }); return pre; @@ -54,7 +60,7 @@ const TavilyForm = () => { ); }, []); - useWatchFormChange(form); + useWatchFormChange(node?.id, form); return (
@@ -92,7 +98,7 @@ const TavilyForm = () => { @@ -104,12 +110,12 @@ const TavilyForm = () => { name="topic" render={({ field }) => ( - Topic + TavilyTopic diff --git a/web/src/pages/agent/form/tavily-form/use-values.ts b/web/src/pages/agent/form/tavily-form/use-values.ts index 486c06622..011e04005 100644 --- a/web/src/pages/agent/form/tavily-form/use-values.ts +++ b/web/src/pages/agent/form/tavily-form/use-values.ts @@ -1,59 +1,15 @@ -import { AgentGlobals } from '@/constants/agent'; +import { RAGFlowNodeType } from '@/interfaces/database/agent'; import { isEmpty } from 'lodash'; import { useMemo } from 'react'; -import useGraphStore from '../../store'; -import { convertToObjectArray, getAgentNodeTools } from '../../utils'; - -export enum SearchDepth { - Basic = 'basic', - Advanced = 'advanced', -} - -export enum Topic { - News = 'news', - General = 'general', -} - -export const defaultValues = { - api_key: '', - query: AgentGlobals.SysQuery, - search_depth: SearchDepth.Basic, - topic: Topic.General, - max_results: 5, - days: 7, - include_answer: false, - include_raw_content: true, - include_images: false, - include_image_descriptions: false, - include_domains: [], - exclude_domains: [], - outputs: { - formalized_content: { - value: '', - type: 'string', - }, - json: { - value: {}, - type: 'Object', - }, - }, -}; - -export function useValues() { - const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore( - (state) => state, - ); +import { initialTavilyValues } from '../../constant'; +import { convertToObjectArray } from '../../utils'; +export function useValues(node?: RAGFlowNodeType) { const values = useMemo(() => { - const agentNode = findUpstreamNodeById(clickedNodeId); - const tools = getAgentNodeTools(agentNode); - - const formData = tools.find( - (x) => x.component_name === clickedToolId, - )?.params; + const formData = node?.data?.form; if (isEmpty(formData)) { - return defaultValues; + return initialTavilyValues; } return { @@ -61,7 +17,7 @@ export function useValues() { include_domains: convertToObjectArray(formData.include_domains), exclude_domains: convertToObjectArray(formData.exclude_domains), }; - }, [clickedNodeId, clickedToolId, findUpstreamNodeById]); + }, [node?.data?.form]); return values; } diff --git a/web/src/pages/agent/form/tavily-form/use-watch-change.ts b/web/src/pages/agent/form/tavily-form/use-watch-change.ts index 31927e9ed..8acaaa2f7 100644 --- a/web/src/pages/agent/form/tavily-form/use-watch-change.ts +++ b/web/src/pages/agent/form/tavily-form/use-watch-change.ts @@ -1,41 +1,23 @@ import { useEffect } from 'react'; import { UseFormReturn, useWatch } from 'react-hook-form'; import useGraphStore from '../../store'; -import { convertToStringArray, getAgentNodeTools } from '../../utils'; +import { convertToStringArray } from '../../utils'; -export function useWatchFormChange(form?: UseFormReturn) { +export function useWatchFormChange(id?: string, form?: UseFormReturn) { let values = useWatch({ control: form?.control }); - const { clickedToolId, clickedNodeId, findUpstreamNodeById, updateNodeForm } = - useGraphStore((state) => state); + const updateNodeForm = useGraphStore((state) => state.updateNodeForm); useEffect(() => { - const agentNode = findUpstreamNodeById(clickedNodeId); // Manually triggered form updates are synchronized to the canvas - if (agentNode && form?.formState.isDirty) { - const agentNodeId = agentNode?.id; - const tools = getAgentNodeTools(agentNode); - + if (id && form?.formState.isDirty) { values = form?.getValues(); - const nextTools = tools.map((x) => { - if (x.component_name === clickedToolId) { - return { - ...x, - params: { - ...values, - include_domains: convertToStringArray(values.include_domains), - exclude_domains: convertToStringArray(values.exclude_domains), - }, - }; - } - return x; - }); - - const nextValues = { - ...(agentNode?.data?.form ?? {}), - tools: nextTools, + let nextValues: any = { + ...values, + include_domains: convertToStringArray(values.include_domains), + exclude_domains: convertToStringArray(values.exclude_domains), }; - updateNodeForm(agentNodeId, nextValues); + updateNodeForm(id, nextValues); } - }, [form?.formState.isDirty, updateNodeForm, values]); + }, [form?.formState.isDirty, id, updateNodeForm, values]); } diff --git a/web/src/pages/agent/form/tool-form/constant.ts b/web/src/pages/agent/form/tool-form/constant.ts index a22aa8a2d..4954f51e1 100644 --- a/web/src/pages/agent/form/tool-form/constant.ts +++ b/web/src/pages/agent/form/tool-form/constant.ts @@ -13,9 +13,9 @@ import GoogleForm from '../google-form'; import GoogleScholarForm from '../google-scholar-form'; import PubMedForm from '../pubmed-form'; import RetrievalForm from '../retrieval-form/next'; -import TavilyForm from '../tavily-form'; import WikipediaForm from '../wikipedia-form'; import YahooFinanceForm from '../yahoo-finance-form'; +import TavilyForm from './tavily-form'; export const ToolFormConfigMap = { [Operator.Retrieval]: RetrievalForm, diff --git a/web/src/pages/agent/form/tool-form/tavily-form/index.tsx b/web/src/pages/agent/form/tool-form/tavily-form/index.tsx new file mode 100644 index 000000000..3dde7fde9 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/tavily-form/index.tsx @@ -0,0 +1,60 @@ +import { FormContainer } from '@/components/form-container'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { useValues } from '../use-values'; +import { useWatchFormChange } from '../use-watch-change'; + +const TavilyForm = () => { + const values = useValues(); + + const FormSchema = z.object({ + api_key: z.string(), + }); + + const form = useForm>({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); + + useWatchFormChange(form); + + return ( + + { + e.preventDefault(); + }} + > + + ( + + Api Key + + + + + + )} + /> + + + + ); +}; + +export default TavilyForm; diff --git a/web/src/pages/agent/form/tool-form/use-values.ts b/web/src/pages/agent/form/tool-form/use-values.ts new file mode 100644 index 000000000..f2745d279 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/use-values.ts @@ -0,0 +1,43 @@ +import { isEmpty } from 'lodash'; +import { useMemo } from 'react'; +import useGraphStore from '../../store'; +import { getAgentNodeTools } from '../../utils'; + +export enum SearchDepth { + Basic = 'basic', + Advanced = 'advanced', +} + +export enum Topic { + News = 'news', + General = 'general', +} + +export const defaultValues = { + api_key: '', +}; + +export function useValues() { + const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore( + (state) => state, + ); + + const values = useMemo(() => { + const agentNode = findUpstreamNodeById(clickedNodeId); + const tools = getAgentNodeTools(agentNode); + + const formData = tools.find( + (x) => x.component_name === clickedToolId, + )?.params; + + if (isEmpty(formData)) { + return defaultValues; + } + + return { + ...formData, + }; + }, [clickedNodeId, clickedToolId, findUpstreamNodeById]); + + return values; +} diff --git a/web/src/pages/agent/form/tool-form/use-watch-change.ts b/web/src/pages/agent/form/tool-form/use-watch-change.ts new file mode 100644 index 000000000..81a70a235 --- /dev/null +++ b/web/src/pages/agent/form/tool-form/use-watch-change.ts @@ -0,0 +1,39 @@ +import { useEffect } from 'react'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import useGraphStore from '../../store'; +import { getAgentNodeTools } from '../../utils'; + +export function useWatchFormChange(form?: UseFormReturn) { + let values = useWatch({ control: form?.control }); + const { clickedToolId, clickedNodeId, findUpstreamNodeById, updateNodeForm } = + useGraphStore((state) => state); + + useEffect(() => { + const agentNode = findUpstreamNodeById(clickedNodeId); + // Manually triggered form updates are synchronized to the canvas + if (agentNode && form?.formState.isDirty) { + const agentNodeId = agentNode?.id; + const tools = getAgentNodeTools(agentNode); + + values = form?.getValues(); + const nextTools = tools.map((x) => { + if (x.component_name === clickedToolId) { + return { + ...x, + params: { + ...values, + }, + }; + } + return x; + }); + + const nextValues = { + ...(agentNode?.data?.form ?? {}), + tools: nextTools, + }; + + updateNodeForm(agentNodeId, nextValues); + } + }, [form?.formState.isDirty, updateNodeForm, values]); +} diff --git a/web/src/pages/agent/hooks.tsx b/web/src/pages/agent/hooks.tsx index 7d194fef0..f10a72ecc 100644 --- a/web/src/pages/agent/hooks.tsx +++ b/web/src/pages/agent/hooks.tsx @@ -64,6 +64,7 @@ import { initialRetrievalValues, initialRewriteQuestionValues, initialSwitchValues, + initialTavilyValues, initialTemplateValues, initialTuShareValues, initialWaitingDialogueValues, @@ -147,6 +148,7 @@ export const useInitializeOperatorParams = () => { [Operator.Code]: initialCodeValues, [Operator.WaitingDialogue]: initialWaitingDialogueValues, [Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, + [Operator.Tavily]: initialTavilyValues, }; }, [llmId]); diff --git a/web/src/pages/agent/hooks/use-add-node.ts b/web/src/pages/agent/hooks/use-add-node.ts index 074e1b201..0a74eac22 100644 --- a/web/src/pages/agent/hooks/use-add-node.ts +++ b/web/src/pages/agent/hooks/use-add-node.ts @@ -39,6 +39,7 @@ import { initialRetrievalValues, initialRewriteQuestionValues, initialSwitchValues, + initialTavilyValues, initialTemplateValues, initialTuShareValues, initialWaitingDialogueValues, @@ -104,6 +105,7 @@ export const useInitializeOperatorParams = () => { [Operator.WaitingDialogue]: initialWaitingDialogueValues, [Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, [Operator.Tool]: {}, + [Operator.Tavily]: initialTavilyValues, }; }, [llmId]);