mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Allow users to select prompt word templates in agent operators. #9935 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -51,6 +51,7 @@ export const enum AgentApiAction {
|
|||||||
FetchAgentAvatar = 'fetchAgentAvatar',
|
FetchAgentAvatar = 'fetchAgentAvatar',
|
||||||
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
||||||
SetAgentSetting = 'setAgentSetting',
|
SetAgentSetting = 'setAgentSetting',
|
||||||
|
FetchPrompt = 'fetchPrompt',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyDsl = {
|
export const EmptyDsl = {
|
||||||
@ -637,3 +638,24 @@ export const useSetAgentSetting = () => {
|
|||||||
|
|
||||||
return { data, loading, setAgentSetting: mutateAsync };
|
return { data, loading, setAgentSetting: mutateAsync };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useFetchPrompt = () => {
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isFetching: loading,
|
||||||
|
refetch,
|
||||||
|
} = useQuery<Record<string, string>>({
|
||||||
|
queryKey: [AgentApiAction.FetchPrompt],
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
gcTime: 0,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await agentService.fetchPrompt();
|
||||||
|
|
||||||
|
return data?.data ?? {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading, refetch };
|
||||||
|
};
|
||||||
|
|||||||
@ -1518,6 +1518,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
sqlStatement: 'SQL Statement',
|
sqlStatement: 'SQL Statement',
|
||||||
sqlStatementTip:
|
sqlStatementTip:
|
||||||
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
|
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
|
||||||
|
frameworkPrompts: 'Framework Prompts',
|
||||||
},
|
},
|
||||||
llmTools: {
|
llmTools: {
|
||||||
bad_calculator: {
|
bad_calculator: {
|
||||||
|
|||||||
@ -1433,6 +1433,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
sqlStatement: 'SQL 语句',
|
sqlStatement: 'SQL 语句',
|
||||||
sqlStatementTip:
|
sqlStatementTip:
|
||||||
'在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。',
|
'在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。',
|
||||||
|
frameworkPrompts: '框架提示词',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import { Output } from '../components/output';
|
|||||||
import { PromptEditor } from '../components/prompt-editor';
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
import { QueryVariable } from '../components/query-variable';
|
import { QueryVariable } from '../components/query-variable';
|
||||||
import { AgentTools, Agents } from './agent-tools';
|
import { AgentTools, Agents } from './agent-tools';
|
||||||
|
import { useBuildPromptExtraPromptOptions } from './use-build-prompt-options';
|
||||||
import { useValues } from './use-values';
|
import { useValues } from './use-values';
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
import { useWatchFormChange } from './use-watch-change';
|
||||||
|
|
||||||
@ -85,6 +86,9 @@ function AgentForm({ node }: INextOperatorForm) {
|
|||||||
|
|
||||||
const defaultValues = useValues(node);
|
const defaultValues = useValues(node);
|
||||||
|
|
||||||
|
const { extraOptions } = useBuildPromptExtraPromptOptions();
|
||||||
|
console.log('🚀 ~ AgentForm ~ prompts:', extraOptions);
|
||||||
|
|
||||||
const ExceptionMethodOptions = Object.values(AgentExceptionMethod).map(
|
const ExceptionMethodOptions = Object.values(AgentExceptionMethod).map(
|
||||||
(x) => ({
|
(x) => ({
|
||||||
label: t(`flow.${x}`),
|
label: t(`flow.${x}`),
|
||||||
@ -150,6 +154,7 @@ function AgentForm({ node }: INextOperatorForm) {
|
|||||||
{...field}
|
{...field}
|
||||||
placeholder={t('flow.messagePlaceholder')}
|
placeholder={t('flow.messagePlaceholder')}
|
||||||
showToolbar={false}
|
showToolbar={false}
|
||||||
|
extraOptions={extraOptions}
|
||||||
></PromptEditor>
|
></PromptEditor>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { useFetchPrompt } from '@/hooks/use-agent-request';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const PromptIdentity = 'RAGFlow-Prompt';
|
||||||
|
|
||||||
|
function wrapPromptWithTag(text: string, tag: string) {
|
||||||
|
const capitalTag = tag.toUpperCase();
|
||||||
|
return `<${capitalTag}>
|
||||||
|
${text}
|
||||||
|
</${capitalTag}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBuildPromptExtraPromptOptions() {
|
||||||
|
const { data: prompts } = useFetchPrompt();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return Object.entries(prompts || {}).map(([key, value]) => ({
|
||||||
|
label: key,
|
||||||
|
value: wrapPromptWithTag(value, key),
|
||||||
|
}));
|
||||||
|
}, [prompts]);
|
||||||
|
|
||||||
|
const extraOptions = [
|
||||||
|
{ label: PromptIdentity, title: t('flow.frameworkPrompts'), options },
|
||||||
|
];
|
||||||
|
|
||||||
|
return { extraOptions };
|
||||||
|
}
|
||||||
@ -29,7 +29,9 @@ import { PasteHandlerPlugin } from './paste-handler-plugin';
|
|||||||
import theme from './theme';
|
import theme from './theme';
|
||||||
import { VariableNode } from './variable-node';
|
import { VariableNode } from './variable-node';
|
||||||
import { VariableOnChangePlugin } from './variable-on-change-plugin';
|
import { VariableOnChangePlugin } from './variable-on-change-plugin';
|
||||||
import VariablePickerMenuPlugin from './variable-picker-plugin';
|
import VariablePickerMenuPlugin, {
|
||||||
|
VariablePickerMenuPluginProps,
|
||||||
|
} from './variable-picker-plugin';
|
||||||
|
|
||||||
// Catch any errors that occur during Lexical updates and log them
|
// Catch any errors that occur during Lexical updates and log them
|
||||||
// or throw them as needed. If you don't throw them, Lexical will
|
// or throw them as needed. If you don't throw them, Lexical will
|
||||||
@ -52,7 +54,8 @@ type IProps = {
|
|||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (value?: string) => void;
|
onChange?: (value?: string) => void;
|
||||||
placeholder?: ReactNode;
|
placeholder?: ReactNode;
|
||||||
} & PromptContentProps;
|
} & PromptContentProps &
|
||||||
|
Pick<VariablePickerMenuPluginProps, 'extraOptions'>;
|
||||||
|
|
||||||
function PromptContent({
|
function PromptContent({
|
||||||
showToolbar = true,
|
showToolbar = true,
|
||||||
@ -122,6 +125,7 @@ export function PromptEditor({
|
|||||||
placeholder,
|
placeholder,
|
||||||
showToolbar,
|
showToolbar,
|
||||||
multiLine = true,
|
multiLine = true,
|
||||||
|
extraOptions,
|
||||||
}: IProps) {
|
}: IProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const initialConfig: InitialConfigType = {
|
const initialConfig: InitialConfigType = {
|
||||||
@ -170,7 +174,10 @@ export function PromptEditor({
|
|||||||
}
|
}
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
/>
|
/>
|
||||||
<VariablePickerMenuPlugin value={value}></VariablePickerMenuPlugin>
|
<VariablePickerMenuPlugin
|
||||||
|
value={value}
|
||||||
|
extraOptions={extraOptions}
|
||||||
|
></VariablePickerMenuPlugin>
|
||||||
<PasteHandlerPlugin />
|
<PasteHandlerPlugin />
|
||||||
<VariableOnChangePlugin
|
<VariableOnChangePlugin
|
||||||
onChange={onValueChange}
|
onChange={onValueChange}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { BeginId } from '@/pages/flow/constant';
|
|
||||||
import { DecoratorNode, LexicalNode, NodeKey } from 'lexical';
|
import { DecoratorNode, LexicalNode, NodeKey } from 'lexical';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
const prefix = BeginId + '@';
|
|
||||||
|
|
||||||
export class VariableNode extends DecoratorNode<ReactNode> {
|
export class VariableNode extends DecoratorNode<ReactNode> {
|
||||||
__value: string;
|
__value: string;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { EditorState, LexicalEditor } from 'lexical';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { ProgrammaticTag } from './constant';
|
import { ProgrammaticTag } from './constant';
|
||||||
|
|
||||||
interface IProps {
|
interface VariableOnChangePluginProps {
|
||||||
onChange: (
|
onChange: (
|
||||||
editorState: EditorState,
|
editorState: EditorState,
|
||||||
editor?: LexicalEditor,
|
editor?: LexicalEditor,
|
||||||
@ -11,7 +11,9 @@ interface IProps {
|
|||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VariableOnChangePlugin({ onChange }: IProps) {
|
export function VariableOnChangePlugin({
|
||||||
|
onChange,
|
||||||
|
}: VariableOnChangePluginProps) {
|
||||||
// Access the editor through the LexicalComposerContext
|
// Access the editor through the LexicalComposerContext
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
// Wrap our listener in useEffect to handle the teardown and avoid stale references.
|
// Wrap our listener in useEffect to handle the teardown and avoid stale references.
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import * as ReactDOM from 'react-dom';
|
|||||||
import { $createVariableNode } from './variable-node';
|
import { $createVariableNode } from './variable-node';
|
||||||
|
|
||||||
import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query';
|
import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query';
|
||||||
|
import { PromptIdentity } from '../../agent-form/use-build-prompt-options';
|
||||||
import { ProgrammaticTag } from './constant';
|
import { ProgrammaticTag } from './constant';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
class VariableInnerOption extends MenuOption {
|
class VariableInnerOption extends MenuOption {
|
||||||
@ -108,11 +109,18 @@ function VariablePickerMenuItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type VariablePickerMenuPluginProps = {
|
||||||
|
value?: string;
|
||||||
|
extraOptions?: Array<{
|
||||||
|
label: string;
|
||||||
|
title: string;
|
||||||
|
options: Array<{ label: string; value: string; icon?: ReactNode }>;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
export default function VariablePickerMenuPlugin({
|
export default function VariablePickerMenuPlugin({
|
||||||
value,
|
value,
|
||||||
}: {
|
extraOptions,
|
||||||
value?: string;
|
}: VariablePickerMenuPluginProps): JSX.Element {
|
||||||
}): JSX.Element {
|
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const isFirstRender = useRef(true);
|
const isFirstRender = useRef(true);
|
||||||
|
|
||||||
@ -122,10 +130,10 @@ export default function VariablePickerMenuPlugin({
|
|||||||
|
|
||||||
const [queryString, setQueryString] = React.useState<string | null>('');
|
const [queryString, setQueryString] = React.useState<string | null>('');
|
||||||
|
|
||||||
const options = useBuildQueryVariableOptions();
|
let options = useBuildQueryVariableOptions();
|
||||||
|
|
||||||
const buildNextOptions = useCallback(() => {
|
const buildNextOptions = useCallback(() => {
|
||||||
let filteredOptions = options;
|
let filteredOptions = [...options, ...(extraOptions ?? [])];
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
const lowerQuery = queryString.toLowerCase();
|
const lowerQuery = queryString.toLowerCase();
|
||||||
filteredOptions = options
|
filteredOptions = options
|
||||||
@ -140,7 +148,7 @@ export default function VariablePickerMenuPlugin({
|
|||||||
.filter((x) => x.options.length > 0);
|
.filter((x) => x.options.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextOptions: VariableOption[] = filteredOptions.map(
|
const finalOptions: VariableOption[] = filteredOptions.map(
|
||||||
(x) =>
|
(x) =>
|
||||||
new VariableOption(
|
new VariableOption(
|
||||||
x.label,
|
x.label,
|
||||||
@ -150,8 +158,8 @@ export default function VariablePickerMenuPlugin({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return nextOptions;
|
return finalOptions;
|
||||||
}, [options, queryString]);
|
}, [extraOptions, options, queryString]);
|
||||||
|
|
||||||
const findItemByValue = useCallback(
|
const findItemByValue = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
@ -173,7 +181,7 @@ export default function VariablePickerMenuPlugin({
|
|||||||
|
|
||||||
const onSelectOption = useCallback(
|
const onSelectOption = useCallback(
|
||||||
(
|
(
|
||||||
selectedOption: VariableOption | VariableInnerOption,
|
selectedOption: VariableInnerOption,
|
||||||
nodeToRemove: TextNode | null,
|
nodeToRemove: TextNode | null,
|
||||||
closeMenu: () => void,
|
closeMenu: () => void,
|
||||||
) => {
|
) => {
|
||||||
@ -193,7 +201,11 @@ export default function VariablePickerMenuPlugin({
|
|||||||
selectedOption.parentLabel as string | ReactNode,
|
selectedOption.parentLabel as string | ReactNode,
|
||||||
selectedOption.icon as ReactNode,
|
selectedOption.icon as ReactNode,
|
||||||
);
|
);
|
||||||
selection.insertNodes([variableNode]);
|
if (selectedOption.parentLabel === PromptIdentity) {
|
||||||
|
selection.insertText(selectedOption.value);
|
||||||
|
} else {
|
||||||
|
selection.insertNodes([variableNode]);
|
||||||
|
}
|
||||||
|
|
||||||
closeMenu();
|
closeMenu();
|
||||||
});
|
});
|
||||||
@ -269,7 +281,13 @@ export default function VariablePickerMenuPlugin({
|
|||||||
return (
|
return (
|
||||||
<LexicalTypeaheadMenuPlugin<VariableOption | VariableInnerOption>
|
<LexicalTypeaheadMenuPlugin<VariableOption | VariableInnerOption>
|
||||||
onQueryChange={setQueryString}
|
onQueryChange={setQueryString}
|
||||||
onSelectOption={onSelectOption}
|
onSelectOption={(option, textNodeContainingQuery, closeMenu) =>
|
||||||
|
onSelectOption(
|
||||||
|
option as VariableInnerOption, // Only the second level menu can be selected
|
||||||
|
textNodeContainingQuery,
|
||||||
|
closeMenu,
|
||||||
|
)
|
||||||
|
}
|
||||||
triggerFn={checkForTriggerMatch}
|
triggerFn={checkForTriggerMatch}
|
||||||
options={buildNextOptions()}
|
options={buildNextOptions()}
|
||||||
menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) => {
|
menuRenderFn={(anchorElementRef, { selectOptionAndCleanUp }) => {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ const {
|
|||||||
fetchAgentAvatar,
|
fetchAgentAvatar,
|
||||||
fetchAgentLogs,
|
fetchAgentLogs,
|
||||||
fetchExternalAgentInputs,
|
fetchExternalAgentInputs,
|
||||||
|
prompt,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -112,6 +113,10 @@ const methods = {
|
|||||||
url: fetchExternalAgentInputs,
|
url: fetchExternalAgentInputs,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
|
fetchPrompt: {
|
||||||
|
url: prompt,
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const agentService = registerNextServer<keyof typeof methods>(methods);
|
const agentService = registerNextServer<keyof typeof methods>(methods);
|
||||||
|
|||||||
@ -164,6 +164,7 @@ export default {
|
|||||||
`${api_host}/canvas/${canvasId}/sessions`,
|
`${api_host}/canvas/${canvasId}/sessions`,
|
||||||
fetchExternalAgentInputs: (canvasId: string) =>
|
fetchExternalAgentInputs: (canvasId: string) =>
|
||||||
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
||||||
|
prompt: `${api_host}/canvas/prompts`,
|
||||||
|
|
||||||
// mcp server
|
// mcp server
|
||||||
listMcpServer: `${api_host}/mcp_server/list`,
|
listMcpServer: `${api_host}/mcp_server/list`,
|
||||||
|
|||||||
Reference in New Issue
Block a user