diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SearchSwitch.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SearchSwitch.tsx
new file mode 100644
index 00000000000..f5bab084d42
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SearchSwitch.tsx
@@ -0,0 +1,32 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import React, { useState } from 'react';
+import { Button, Tooltip } from 'antd';
+import { GlobalOutlined } from '@ant-design/icons';
+import { useChatConversationOptions } from './hooks/useChatConversationOptions';
+import { useT } from '../../locale';
+
+export const SearchSwitch: React.FC = () => {
+ const t = useT();
+ const { loading, webSearch, updateWebSearch } = useChatConversationOptions();
+ const switchChecked = () => {
+ updateWebSearch(!webSearch);
+ };
+
+ return webSearch ? (
+
+ } onClick={switchChecked} loading={loading} />
+
+ ) : (
+
+ } onClick={switchChecked} loading={loading} />
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Sender.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Sender.tsx
index 76f3d7d1f57..1b60f7654a7 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Sender.tsx
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Sender.tsx
@@ -26,6 +26,7 @@ const useSendMessage = () => {
const editingMessageId = useChatBoxStore.use.editingMessageId();
const currentConversation = useChatConversationsStore.use.currentConversation();
+ const webSearch = useChatConversationsStore.use.webSearch();
const attachments = useChatMessagesStore.use.attachments();
const contextItems = useChatMessagesStore.use.contextItems();
@@ -50,6 +51,7 @@ const useSendMessage = () => {
workContext: contextItems,
editingMessageId: isEditingMessage ? editingMessageId : undefined,
skillSettings,
+ webSearch,
});
if (isEditingMessage) {
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SenderFooter.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SenderFooter.tsx
index 6086e4db9a0..33c66bca499 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SenderFooter.tsx
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/SenderFooter.tsx
@@ -14,6 +14,7 @@ import { AddContextButton } from '../AddContextButton';
import { useChatMessagesStore } from './stores/chat-messages';
import { useChatBoxStore } from './stores/chat-box';
import _ from 'lodash';
+import { SearchSwitch } from './SearchSwitch';
export const SenderFooter: React.FC<{
components: any;
@@ -60,6 +61,7 @@ export const SenderFooter: React.FC<{
/>
+ {currentEmployee?.webSearch && }
{loading ? (
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatBoxActions.ts b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatBoxActions.ts
index 3a068e114d3..5f27647851a 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatBoxActions.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatBoxActions.ts
@@ -179,6 +179,7 @@ export const useChatBoxActions = () => {
attachments,
workContext,
skillSettings,
+ webSearch: false,
});
}
return;
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatConversationOptions.ts b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatConversationOptions.ts
new file mode 100644
index 00000000000..68422376d8a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatConversationOptions.ts
@@ -0,0 +1,63 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { useAPIClient } from '@nocobase/client';
+import { useChatConversationsStore } from '../stores/chat-conversations';
+import { useEffect, useState } from 'react';
+
+export const useChatConversationOptions = () => {
+ const api = useAPIClient();
+ const sessionId = useChatConversationsStore.use.currentConversation();
+ const webSearch = useChatConversationsStore.use.webSearch();
+ const setWebSearch = useChatConversationsStore.use.setWebSearch();
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (!sessionId) {
+ return;
+ }
+ setLoading(true);
+ api
+ .resource('aiConversations')
+ .get({
+ filterByTk: sessionId,
+ })
+ .then(({ data }) => {
+ const { conversationSettings } = data?.data?.options ?? {};
+ const { webSearch } = conversationSettings ?? { webSearch: true };
+ setWebSearch(webSearch);
+ })
+ .finally(() => {
+ setLoading(false);
+ })
+ .catch(() => {});
+ }, [api, sessionId, setWebSearch]);
+
+ const updateWebSearch = async (webSearch: boolean) => {
+ if (sessionId) {
+ setLoading(true);
+ await api.resource('aiConversations').updateOptions({
+ filterByTk: sessionId,
+ values: {
+ conversationSettings: { webSearch },
+ },
+ });
+ setWebSearch(webSearch);
+ setLoading(false);
+ } else {
+ setWebSearch(webSearch);
+ }
+ };
+
+ return {
+ loading,
+ webSearch,
+ updateWebSearch,
+ };
+};
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatMessageActions.ts b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatMessageActions.ts
index 1ac9778b998..7b9f06e4f61 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatMessageActions.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/hooks/useChatMessageActions.ts
@@ -208,6 +208,7 @@ export const useChatMessageActions = () => {
editingMessageId,
onConversationCreate,
skillSettings,
+ webSearch,
}: SendOptions & {
onConversationCreate?: (sessionId: string) => void;
}) => {
@@ -240,7 +241,7 @@ export const useChatMessageActions = () => {
if (!sessionId) {
const createRes = await api.resource('aiConversations').create({
- values: { aiEmployee, systemMessage, skillSettings },
+ values: { aiEmployee, systemMessage, skillSettings, conversationSettings: { webSearch } },
});
const conversation = createRes?.data?.data;
if (!conversation) return;
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/stores/chat-conversations.ts b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/stores/chat-conversations.ts
index 920b3111040..b2df7f8fa01 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/stores/chat-conversations.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/stores/chat-conversations.ts
@@ -15,18 +15,21 @@ interface ChatConversationsState {
currentConversation?: string;
conversations: Conversation[];
keyword: string;
+ webSearch: boolean;
}
interface ChatConversationsActions {
setCurrentConversation: (id: string | undefined) => void;
setKeyword: (keyword: string) => void;
setConversations: (conversations: Conversation[] | ((prev: Conversation[]) => Conversation[])) => void;
+ setWebSearch: (webSearch: boolean) => void;
}
const store = create((set) => ({
currentConversation: undefined,
conversations: [],
keyword: '',
+ webSearch: true,
setCurrentConversation: (id) => set({ currentConversation: id }),
setKeyword: (keyword) => set({ keyword }),
@@ -34,6 +37,7 @@ const store = create((set) =>
set((state) => ({
conversations: typeof conversations === 'function' ? conversations(state.conversations) : conversations,
})),
+ setWebSearch: (webSearch) => set({ webSearch }),
}));
export const useChatConversationsStore = createSelectors(store);
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/types.ts b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/types.ts
index b7d190686dc..ca9cc87dc65 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/types.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/types.ts
@@ -30,6 +30,7 @@ export type AIEmployee = {
skills?: { name: string }[];
};
builtIn?: boolean;
+ webSearch?: boolean;
};
export type SkillSettings = {
@@ -43,6 +44,16 @@ export type Conversation = {
aiEmployee: AIEmployee;
};
+export type ConversationOptions = {
+ sessionId: string;
+ options: {
+ conversationSetting?: ConversationSetting;
+ };
+};
+export type ConversationSetting = {
+ webSearch?: boolean;
+};
+
export type ContextItem = {
type: string;
uid: string;
@@ -106,6 +117,7 @@ export type SendOptions = {
workContext: ContextItem[];
editingMessageId?: string;
skillSettings?: SkillSettings;
+ webSearch?: boolean;
};
export type ResendOptions = {
diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/llm-providers/dashscope/ModelSettings.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/llm-providers/dashscope/ModelSettings.tsx
index 3f76198eee0..266ea66140d 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/client/llm-providers/dashscope/ModelSettings.tsx
+++ b/packages/plugins/@nocobase/plugin-ai/src/client/llm-providers/dashscope/ModelSettings.tsx
@@ -11,7 +11,7 @@ import React from 'react';
import { SchemaComponent } from '@nocobase/client';
import { tval } from '@nocobase/utils/client';
import { namespace, useT } from '../../locale';
-import { Collapse } from 'antd';
+import { Collapse, Switch } from 'antd';
import { WorkflowVariableRawTextArea } from '@nocobase/plugin-workflow/client';
import { ModelSelect } from '../components/ModelSelect';
@@ -147,7 +147,7 @@ const Options: React.FC = () => {
export const ModelSettingsForm: React.FC = () => {
return (
{
'x-decorator': 'FormItem',
'x-component': 'ModelSelect',
},
+ builtIn: {
+ title: tval('Built in tools', { ns: namespace }),
+ type: 'object',
+ properties: {
+ webSearch: {
+ type: 'boolean',
+ title: tval('Web search', { ns: namespace }),
+ 'x-decorator': 'FormItem',
+ 'x-decorator-props': {
+ layout: 'horizontal',
+ },
+ 'x-component': 'Switch',
+ },
+ },
+ },
options: {
type: 'void',
'x-component': 'Options',
diff --git a/packages/plugins/@nocobase/plugin-ai/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-ai/src/locale/en-US.json
index f5a6c319914..2fd36a1bb17 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/locale/en-US.json
+++ b/packages/plugins/@nocobase/plugin-ai/src/locale/en-US.json
@@ -179,5 +179,7 @@
"Auto usage": "Auto usage",
"Use skills": "Use skills",
"Cite {{count}} pieces of information as references": "Cite {{count}} pieces of information as references",
- "references {{index}}": "references {{index}}"
+ "references {{index}}": "references {{index}}",
+ "Enable search": "Enable search",
+ "Disable search": "Disable search"
}
diff --git a/packages/plugins/@nocobase/plugin-ai/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-ai/src/locale/zh-CN.json
index 90c8afd4ca6..b6a21b09fe6 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/locale/zh-CN.json
+++ b/packages/plugins/@nocobase/plugin-ai/src/locale/zh-CN.json
@@ -180,5 +180,7 @@
"Auto usage": "自动使用",
"Use skills": "使用技能",
"Cite {{count}} pieces of information as references": "引用 {{count}} 篇资料作为参考",
- "references {{index}}": "参考资料 {{index}}"
+ "references {{index}}": "参考资料 {{index}}",
+ "Enable search": "启用搜索",
+ "Disable search": "禁用搜索"
}
diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/ai-employees/ai-employee.ts b/packages/plugins/@nocobase/plugin-ai/src/server/ai-employees/ai-employee.ts
index fb9f358d6f2..5847a7394ca 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/server/ai-employees/ai-employee.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/server/ai-employees/ai-employee.ts
@@ -48,6 +48,7 @@ export class AIEmployee {
private systemMessage: string;
private aiChatConversation: AIChatConversation;
private skillSettings?: Record;
+ private webSearch?: boolean;
constructor(
ctx: Context,
@@ -55,6 +56,7 @@ export class AIEmployee {
sessionId: string,
systemMessage?: string,
skillSettings?: Record,
+ webSearch?: boolean,
) {
this.employee = employee;
this.ctx = ctx;
@@ -68,6 +70,7 @@ export class AIEmployee {
const locale = this.ctx.getCurrentLocale();
const builtInManager = this.plugin.builtInManager;
builtInManager.setupBuiltInInfo(locale, this.employee as unknown as AIEmployeeType);
+ this.webSearch = webSearch;
}
async getLLMService() {
@@ -77,6 +80,14 @@ export class AIEmployee {
throw new Error('LLM service not configured');
}
+ if (modelSettings?.builtIn?.webSearch === true) {
+ if (this.webSearch !== false) {
+ modelSettings.builtIn.webSearch = true;
+ } else {
+ modelSettings.builtIn.webSearch = false;
+ }
+ }
+
const service = await this.db.getRepository('llmServices').findOne({
filter: {
name: modelSettings.llmService,
diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/dashscope.ts b/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/dashscope.ts
index 10f12b17ed4..6310c072f0c 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/dashscope.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/dashscope.ts
@@ -31,11 +31,21 @@ export class DashscopeProvider extends LLMProvider {
if (responseFormat === 'json_schema' && schema) {
responseFormatOptions['json_schema'] = schema;
}
+
+ const additionalArgs = {};
+ if (this.modelOptions?.builtIn?.webSearch === true) {
+ // enable platform's web search ability
+ // ref: https://bailian.console.aliyun.com/?tab=doc#/doc/?type=model&url=2867560
+ additionalArgs['enable_search'] = true;
+ additionalArgs['search_options'] = { forced_search: true };
+ }
+
return new ChatOpenAI({
apiKey,
...this.modelOptions,
modelKwargs: {
response_format: responseFormatOptions,
+ ...additionalArgs,
},
configuration: {
baseURL: baseURL || this.baseURL,
diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/provider.ts b/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/provider.ts
index 4d82dbf95e4..2fbf34e1aa5 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/provider.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/provider.ts
@@ -53,9 +53,9 @@ export abstract class LLMProvider {
if (!this.isToolConflict() && context.tools?.length) {
tools.push(...context.tools);
}
- chain = chain.bindTools(tools);
+ chain = chain.bindTools?.(tools);
} else if (context.tools?.length) {
- chain = chain.bindTools(context.tools);
+ chain = chain.bindTools?.(context.tools);
}
if (context.structuredOutput) {
diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/manager/ai-chat-conversation.ts b/packages/plugins/@nocobase/plugin-ai/src/server/manager/ai-chat-conversation.ts
index 211756419c5..02736cad9e5 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/server/manager/ai-chat-conversation.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/server/manager/ai-chat-conversation.ts
@@ -125,7 +125,7 @@ class AIChatConversationImpl implements AIChatConversation {
}
return {
messages,
- tools: options?.tools,
+ tools: options.tools,
};
}
diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiConversations.ts b/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiConversations.ts
index 7f65b905e98..0997107edd0 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiConversations.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiConversations.ts
@@ -58,7 +58,7 @@ export default {
async create(ctx: Context, next: Next) {
const userId = ctx.auth?.user.id;
- const { aiEmployee, systemMessage, skillSettings } = ctx.action.params.values || {};
+ const { aiEmployee, systemMessage, skillSettings, conversationSettings } = ctx.action.params.values || {};
const employee = await getAIEmployee(ctx, aiEmployee.username);
if (!employee) {
ctx.throw(400, 'AI employee not found');
@@ -72,6 +72,7 @@ export default {
options: {
systemMessage,
skillSettings,
+ conversationSettings,
},
},
});
@@ -95,6 +96,57 @@ export default {
await next();
},
+ async updateOptions(ctx: Context, next: Next) {
+ const userId = ctx.auth?.user.id;
+ if (!userId) {
+ return ctx.throw(403);
+ }
+
+ const { filterByTk: sessionId } = ctx.action.params;
+ if (!sessionId) {
+ return ctx.throw(400, 'invalid sessionId');
+ }
+
+ const { systemMessage, skillSettings, conversationSettings } = ctx.action.params.values || {};
+ if (!systemMessage && !skillSettings && !conversationSettings) {
+ return ctx.throw(400, 'invalid options');
+ }
+
+ const conversation = await ctx.db.getRepository('aiConversations').findOne({
+ filter: {
+ sessionId,
+ userId,
+ },
+ });
+
+ if (!conversation) {
+ ctx.throw(400, 'invalid sessionId');
+ }
+
+ const options = conversation.options ?? {};
+ if (systemMessage) {
+ options['systemMessage'] = systemMessage;
+ }
+ if (skillSettings) {
+ options['skillSettings'] = skillSettings;
+ }
+ if (conversationSettings) {
+ options['conversationSettings'] = conversationSettings;
+ }
+
+ ctx.body = await ctx.db.getRepository('aiConversations').update({
+ filter: {
+ userId,
+ sessionId,
+ },
+ values: {
+ options,
+ },
+ });
+
+ await next();
+ },
+
async destroy(ctx: Context, next: Next) {
const userId = ctx.auth?.user.id;
if (!userId) {
@@ -299,6 +351,7 @@ export default {
sessionId,
conversation.options?.systemMessage,
conversation.options?.skillSettings,
+ conversation.options?.conversationSettings?.webSearch,
);
await aiEmployee.cancelToolCall();
await aiEmployee.processMessages(messages, editingMessageId);
@@ -363,6 +416,7 @@ export default {
sessionId,
conversation.options?.systemMessage,
conversation.options?.skillSettings,
+ conversation.options?.conversationSettings?.webSearch,
);
await aiEmployee.resendMessages(messageId);
} catch (err) {
@@ -466,6 +520,7 @@ export default {
sessionId,
conversation.options?.systemMessage,
conversation.options?.skillSettings,
+ conversation.options?.conversationSettings?.webSearch,
);
await aiEmployee.callTool(message.messageId, false);
} catch (err) {
@@ -524,6 +579,7 @@ export default {
sessionId,
conversation.options?.systemMessage,
conversation.options?.skillSettings,
+ conversation.options?.conversationSettings?.webSearch,
);
await aiEmployee.confirmToolCall(message.messageId, toolCallIds);
} catch (err) {
diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiEmployees.ts b/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiEmployees.ts
index 48f343d9af7..f24752cf47a 100644
--- a/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiEmployees.ts
+++ b/packages/plugins/@nocobase/plugin-ai/src/server/resource/aiEmployees.ts
@@ -100,6 +100,7 @@ export const listByUser = async (ctx: Context, next: Next) => {
},
skillSettings: row.skillSettings,
builtIn: row.builtIn,
+ webSearch: row.modelSettings?.builtIn?.webSearch ?? false,
}));
await next();
};