diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml index e7a912bff..d5a45444a 100644 --- a/.github/workflows/dev-build.yaml +++ b/.github/workflows/dev-build.yaml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: ['arm-runner-test'] # put your current branch to create a build. Core team only. + branches: ['disable-default-agent-skills'] # put your current branch to create a build. Core team only. paths-ignore: - '**.md' - 'cloud-deployments/*' diff --git a/frontend/src/pages/Admin/Agents/DefaultSkillPanel/index.jsx b/frontend/src/pages/Admin/Agents/DefaultSkillPanel/index.jsx index d28f35cf2..e2505b3ed 100644 --- a/frontend/src/pages/Admin/Agents/DefaultSkillPanel/index.jsx +++ b/frontend/src/pages/Admin/Agents/DefaultSkillPanel/index.jsx @@ -1,7 +1,15 @@ import React from "react"; import { DefaultBadge } from "../Badges/default"; -export default function DefaultSkillPanel({ title, description, image, icon }) { +export default function DefaultSkillPanel({ + title, + description, + image, + icon, + enabled = true, + toggleSkill, + skill, +}) { return ( <div className="p-2"> <div className="flex flex-col gap-y-[18px] max-w-[500px]"> @@ -21,10 +29,26 @@ export default function DefaultSkillPanel({ title, description, image, icon }) { </label> <DefaultBadge title={title} /> </div> + <label + className={`border-none relative inline-flex items-center ml-auto cursor-pointer`} + > + <input + type="checkbox" + className="peer sr-only" + checked={enabled} + onChange={() => toggleSkill(skill)} + /> + <div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div> + <span className="ml-3 text-sm font-medium"></span> + </label> </div> <img src={image} alt={title} className="w-full rounded-md" /> <p className="text-theme-text-secondary text-opacity-60 text-xs font-medium py-1.5"> {description} + <br /> + <br /> + By default, this skill is enabled, but you can disable it if you don't + want it to be available to the agent. </p> </div> </div> diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index e176aeecb..605c89348 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -15,14 +15,16 @@ import ImportedSkillConfig from "./Imported/ImportedSkillConfig"; import { Tooltip } from "react-tooltip"; export default function AdminAgents() { + const formEl = useRef(null); const [hasChanges, setHasChanges] = useState(false); const [settings, setSettings] = useState({}); const [selectedSkill, setSelectedSkill] = useState(""); - const [agentSkills, setAgentSkills] = useState([]); - const [importedSkills, setImportedSkills] = useState([]); const [loading, setLoading] = useState(true); const [showSkillModal, setShowSkillModal] = useState(false); - const formEl = useRef(null); + + const [agentSkills, setAgentSkills] = useState([]); + const [importedSkills, setImportedSkills] = useState([]); + const [disabledAgentSkills, setDisabledAgentSkills] = useState([]); // Alert user if they try to leave the page with unsaved changes useEffect(() => { @@ -42,17 +44,31 @@ export default function AdminAgents() { async function fetchSettings() { const _settings = await System.keys(); const _preferences = await Admin.systemPreferencesByFields([ + "disabled_agent_skills", "default_agent_skills", "imported_agent_skills", ]); setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); setAgentSkills(_preferences.settings?.default_agent_skills ?? []); + setDisabledAgentSkills( + _preferences.settings?.disabled_agent_skills ?? [] + ); setImportedSkills(_preferences.settings?.imported_agent_skills ?? []); setLoading(false); } fetchSettings(); }, []); + const toggleDefaultSkill = (skillName) => { + setDisabledAgentSkills((prev) => { + const updatedSkills = prev.includes(skillName) + ? prev.filter((name) => name !== skillName) + : [...prev, skillName]; + setHasChanges(true); + return updatedSkills; + }); + }; + const toggleAgentSkill = (skillName) => { setAgentSkills((prev) => { const updatedSkills = prev.includes(skillName) @@ -93,11 +109,15 @@ export default function AdminAgents() { if (success) { const _settings = await System.keys(); const _preferences = await Admin.systemPreferencesByFields([ + "disabled_agent_skills", "default_agent_skills", "imported_agent_skills", ]); setSettings({ ..._settings, preferences: _preferences.settings } ?? {}); setAgentSkills(_preferences.settings?.default_agent_skills ?? []); + setDisabledAgentSkills( + _preferences.settings?.disabled_agent_skills ?? [] + ); setImportedSkills(_preferences.settings?.imported_agent_skills ?? []); showToast(`Agent preferences saved successfully.`, "success", { clear: true, @@ -143,6 +163,11 @@ export default function AdminAgents() { type="hidden" value={agentSkills.join(",")} /> + <input + name="system::disabled_agent_skills" + type="hidden" + value={disabledAgentSkills.join(",")} + /> {/* Skill settings nav */} <div hidden={showSkillModal} className="flex flex-col gap-y-[18px]"> @@ -152,13 +177,15 @@ export default function AdminAgents() { </div> {/* Default skills */} <SkillList - isDefault={true} skills={defaultSkills} selectedSkill={selectedSkill} handleClick={(skill) => { setSelectedSkill(skill); setShowSkillModal(true); }} + activeSkills={Object.keys(defaultSkills).filter( + (skill) => !disabledAgentSkills.includes(skill) + )} /> {/* Configurable skills */} <SkillList @@ -212,17 +239,35 @@ export default function AdminAgents() { setImportedSkills={setImportedSkills} /> ) : ( - <SelectedSkillComponent - skill={configurableSkills[selectedSkill]?.skill} - settings={settings} - toggleSkill={toggleAgentSkill} - enabled={agentSkills.includes( - configurableSkills[selectedSkill]?.skill + <> + {defaultSkills?.[selectedSkill] ? ( + // The selected skill is a default skill - show the default skill panel + <SelectedSkillComponent + skill={defaultSkills[selectedSkill]?.skill} + settings={settings} + toggleSkill={toggleDefaultSkill} + enabled={ + !disabledAgentSkills.includes( + defaultSkills[selectedSkill]?.skill + ) + } + setHasChanges={setHasChanges} + {...defaultSkills[selectedSkill]} + /> + ) : ( + // The selected skill is a configurable skill - show the configurable skill panel + <SelectedSkillComponent + skill={configurableSkills[selectedSkill]?.skill} + settings={settings} + toggleSkill={toggleAgentSkill} + enabled={agentSkills.includes( + configurableSkills[selectedSkill]?.skill + )} + setHasChanges={setHasChanges} + {...configurableSkills[selectedSkill]} + /> )} - setHasChanges={setHasChanges} - {...(configurableSkills[selectedSkill] || - defaultSkills[selectedSkill])} - /> + </> )} </> ) : ( @@ -258,6 +303,11 @@ export default function AdminAgents() { type="hidden" value={agentSkills.join(",")} /> + <input + name="system::disabled_agent_skills" + type="hidden" + value={disabledAgentSkills.join(",")} + /> {/* Skill settings nav */} <div className="flex flex-col gap-y-[18px]"> @@ -268,10 +318,12 @@ export default function AdminAgents() { {/* Default skills list */} <SkillList - isDefault={true} skills={defaultSkills} selectedSkill={selectedSkill} handleClick={setSelectedSkill} + activeSkills={Object.keys(defaultSkills).filter( + (skill) => !disabledAgentSkills.includes(skill) + )} /> {/* Configurable skills */} <SkillList @@ -304,17 +356,35 @@ export default function AdminAgents() { setImportedSkills={setImportedSkills} /> ) : ( - <SelectedSkillComponent - skill={configurableSkills[selectedSkill]?.skill} - settings={settings} - toggleSkill={toggleAgentSkill} - enabled={agentSkills.includes( - configurableSkills[selectedSkill]?.skill + <> + {defaultSkills?.[selectedSkill] ? ( + // The selected skill is a default skill - show the default skill panel + <SelectedSkillComponent + skill={defaultSkills[selectedSkill]?.skill} + settings={settings} + toggleSkill={toggleDefaultSkill} + enabled={ + !disabledAgentSkills.includes( + defaultSkills[selectedSkill]?.skill + ) + } + setHasChanges={setHasChanges} + {...defaultSkills[selectedSkill]} + /> + ) : ( + // The selected skill is a configurable skill - show the configurable skill panel + <SelectedSkillComponent + skill={configurableSkills[selectedSkill]?.skill} + settings={settings} + toggleSkill={toggleAgentSkill} + enabled={agentSkills.includes( + configurableSkills[selectedSkill]?.skill + )} + setHasChanges={setHasChanges} + {...configurableSkills[selectedSkill]} + /> )} - setHasChanges={setHasChanges} - {...(configurableSkills[selectedSkill] || - defaultSkills[selectedSkill])} - /> + </> )} </> ) : ( diff --git a/frontend/src/pages/Admin/Agents/skills.js b/frontend/src/pages/Admin/Agents/skills.js index d7dfddfb7..0b73cca67 100644 --- a/frontend/src/pages/Admin/Agents/skills.js +++ b/frontend/src/pages/Admin/Agents/skills.js @@ -23,21 +23,24 @@ export const defaultSkills = { component: DefaultSkillPanel, icon: Brain, image: RAGImage, + skill: "rag-memory", }, - "view-summarize": { + "document-summarizer": { title: "View & summarize documents", description: "Allow the agent to list and summarize the content of workspace files currently embedded.", component: DefaultSkillPanel, icon: File, image: SummarizeImage, + skill: "document-summarizer", }, - "scrape-websites": { + "web-scraping": { title: "Scrape websites", description: "Allow the agent to visit and scrape the content of websites.", component: DefaultSkillPanel, icon: Browser, image: ScrapeWebsitesImage, + skill: "web-scraping", }, }; diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index cf3c310d8..e4e556e57 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -374,6 +374,9 @@ function adminEndpoints(app) { case "default_agent_skills": requestedSettings[label] = safeJsonParse(setting?.value, []); break; + case "disabled_agent_skills": + requestedSettings[label] = safeJsonParse(setting?.value, []); + break; case "imported_agent_skills": requestedSettings[label] = ImportedPlugin.listImportedPlugins(); break; @@ -440,6 +443,12 @@ function adminEndpoints(app) { ?.value, [] ) || [], + disabled_agent_skills: + safeJsonParse( + (await SystemSettings.get({ label: "disabled_agent_skills" })) + ?.value, + [] + ) || [], imported_agent_skills: ImportedPlugin.listImportedPlugins(), custom_app_name: (await SystemSettings.get({ label: "custom_app_name" }))?.value || diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 402fbf8e5..4a862109f 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -24,6 +24,7 @@ const SystemSettings = { "agent_search_provider", "agent_sql_connections", "default_agent_skills", + "disabled_agent_skills", "imported_agent_skills", "custom_app_name", "feature_flags", @@ -40,6 +41,7 @@ const SystemSettings = { "text_splitter_chunk_overlap", "agent_search_provider", "default_agent_skills", + "disabled_agent_skills", "agent_sql_connections", "custom_app_name", @@ -125,6 +127,15 @@ const SystemSettings = { return JSON.stringify([]); } }, + disabled_agent_skills: (updates) => { + try { + const skills = updates.split(",").filter((skill) => !!skill); + return JSON.stringify(skills); + } catch (e) { + console.error(`Could not validate disabled agent skills.`); + return JSON.stringify([]); + } + }, agent_sql_connections: async (updates) => { const existingConnections = safeJsonParse( (await SystemSettings.get({ label: "agent_sql_connections" }))?.value, diff --git a/server/utils/agents/defaults.js b/server/utils/agents/defaults.js index 6154fab66..093982b79 100644 --- a/server/utils/agents/defaults.js +++ b/server/utils/agents/defaults.js @@ -4,6 +4,13 @@ const { safeJsonParse } = require("../http"); const Provider = require("./aibitat/providers/ai-provider"); const ImportedPlugin = require("./imported"); +// This is a list of skills that are built-in and default enabled. +const DEFAULT_SKILLS = [ + AgentPlugins.memory.name, + AgentPlugins.docSummarizer.name, + AgentPlugins.webScraping.name, +]; + const USER_AGENT = { name: "USER", getDefinition: async () => { @@ -17,16 +24,9 @@ const USER_AGENT = { const WORKSPACE_AGENT = { name: "@agent", getDefinition: async (provider = null) => { - const defaultFunctions = [ - AgentPlugins.memory.name, // RAG - AgentPlugins.docSummarizer.name, // Doc Summary - AgentPlugins.webScraping.name, // Collector web-scraping - ]; - return { role: Provider.systemPrompt(provider), functions: [ - ...defaultFunctions, ...(await agentSkillsFromSystemSettings()), ...(await ImportedPlugin.activeImportedPlugins()), ], @@ -41,10 +41,29 @@ const WORKSPACE_AGENT = { */ async function agentSkillsFromSystemSettings() { const systemFunctions = []; - const _setting = (await SystemSettings.get({ label: "default_agent_skills" })) - ?.value; - safeJsonParse(_setting, []).forEach((skillName) => { + // Load non-imported built-in skills that are configurable, but are default enabled. + const _disabledDefaultSkills = safeJsonParse( + await SystemSettings.getValueOrFallback( + { label: "disabled_agent_skills" }, + "[]" + ), + [] + ); + DEFAULT_SKILLS.forEach((skill) => { + if (!_disabledDefaultSkills.includes(skill)) + systemFunctions.push(AgentPlugins[skill].name); + }); + + // Load non-imported built-in skills that are configurable. + const _setting = safeJsonParse( + await SystemSettings.getValueOrFallback( + { label: "default_agent_skills" }, + "[]" + ), + [] + ); + _setting.forEach((skillName) => { if (!AgentPlugins.hasOwnProperty(skillName)) return; // This is a plugin module with many sub-children plugins who