diff --git a/.vscode/settings.json b/.vscode/settings.json index 549fd15742..4769a939c4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,7 @@ "Qdrant", "royalblue", "searxng", + "SearchApi", "Serper", "Serply", "streamable", diff --git a/README.md b/README.md index 5c4e9cccb5..e076332663 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace ### Supported LLMs, Embedder Models, Speech models, and Vector Databases -**Language Learning Models:** +**Large Language Models (LLMs):** - [Any open-source llama.cpp compatible model](/server/storage/models/README.md#text-generation-llm-selection) - [OpenAI](https://openai.com) diff --git a/collector/extensions/index.js b/collector/extensions/index.js index 30beaa3e72..47989d5d5c 100644 --- a/collector/extensions/index.js +++ b/collector/extensions/index.js @@ -38,7 +38,6 @@ function extensions(app) { reqBody(request), response, ); - console.log({ success, reason, data }) response.status(200).json({ success, reason, diff --git a/docker/.env.example b/docker/.env.example index 56be87cb4e..1521a307af 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -252,6 +252,10 @@ GID='1000' # AGENT_GSE_KEY= # AGENT_GSE_CTX= +#------ SearchApi.io ----------- https://www.searchapi.io/ +# AGENT_SEARCHAPI_API_KEY= +# AGENT_SEARCHAPI_ENGINE=google + #------ Serper.dev ----------- https://serper.dev/ # AGENT_SERPER_DEV_KEY= diff --git a/docker/HOW_TO_USE_DOCKER.md b/docker/HOW_TO_USE_DOCKER.md index 1e95bd8a04..2eeaee060f 100644 --- a/docker/HOW_TO_USE_DOCKER.md +++ b/docker/HOW_TO_USE_DOCKER.md @@ -117,8 +117,8 @@ services: - WHISPER_PROVIDER=local - TTS_PROVIDER=native - PASSWORDMINCHAR=8 - - AGENT_SERPER_DEV_KEY="SERPER DEV API KEY" - - AGENT_SERPLY_API_KEY="Serply.io API KEY" + # Add any other keys here for services or settings + # you can find in the docker/.env.example file volumes: - anythingllm_storage:/app/server/storage restart: always diff --git a/frontend/package.json b/frontend/package.json index 8a60c11095..524f90fa6c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,6 @@ "preview": "vite preview" }, "dependencies": { - "@metamask/jazzicon": "^2.0.0", "@microsoft/fetch-event-source": "^2.0.1", "@mintplex-labs/piper-tts-web": "^1.0.4", "@phosphor-icons/react": "^2.1.7", @@ -71,4 +70,4 @@ "tailwindcss": "^3.3.1", "vite": "^4.3.0" } -} +} \ No newline at end of file diff --git a/frontend/src/components/Modals/ManageWorkspace/index.jsx b/frontend/src/components/Modals/ManageWorkspace/index.jsx index 2c6e658b04..fe3f539b91 100644 --- a/frontend/src/components/Modals/ManageWorkspace/index.jsx +++ b/frontend/src/components/Modals/ManageWorkspace/index.jsx @@ -127,19 +127,32 @@ const ModalTabSwitcher = ({ selectedTab, setSelectedTab }) => { ); }; + export function useManageWorkspaceModal() { const { user } = useUser(); const [showing, setShowing] = useState(false); - const showModal = () => { + function showModal() { if (user?.role !== "default") { setShowing(true); } - }; + } - const hideModal = () => { + function hideModal() { setShowing(false); - }; + } + + useEffect(() => { + function onEscape(event) { + if (!showing || event.key !== "Escape") return; + setShowing(false); + } + + document.addEventListener("keydown", onEscape); + return () => { + document.removeEventListener("keydown", onEscape); + }; + }, [showing]); return { showing, showModal, hideModal }; } diff --git a/frontend/src/components/UserIcon/index.jsx b/frontend/src/components/UserIcon/index.jsx index 7fc6b8df6d..037a2aff08 100644 --- a/frontend/src/components/UserIcon/index.jsx +++ b/frontend/src/components/UserIcon/index.jsx @@ -1,35 +1,42 @@ -import React, { useRef, useEffect } from "react"; -import JAZZ from "@metamask/jazzicon"; +import React, { memo } from "react"; import usePfp from "../../hooks/usePfp"; +import UserDefaultPfp from "./user.svg"; +import WorkspaceDefaultPfp from "./workspace.svg"; -export default function UserIcon({ size = 36, user, role }) { +const UserIcon = memo(({ role }) => { const { pfp } = usePfp(); - const divRef = useRef(null); - const seed = user?.uid - ? toPseudoRandomInteger(user.uid) - : Math.floor(100000 + Math.random() * 900000); - - useEffect(() => { - if (!divRef.current || (role === "user" && pfp)) return; - - const result = JAZZ(size, seed); - divRef.current.appendChild(result); - }, [pfp, role, seed, size]); return (
-
- {role === "user" && pfp && ( + {role === "user" && } + {role !== "user" && ( User profile picture )}
); -} +}); + +function RenderUserPfp({ pfp }) { + if (!pfp) + return ( + User profile picture + ); -function toPseudoRandomInteger(uidString = "") { - return uidString.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0); + return ( + User profile picture + ); } + +export default UserIcon; diff --git a/frontend/src/components/UserIcon/user.svg b/frontend/src/components/UserIcon/user.svg new file mode 100644 index 0000000000..b0812cd2b4 --- /dev/null +++ b/frontend/src/components/UserIcon/user.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/src/components/UserIcon/workspace.png b/frontend/src/components/UserIcon/workspace.png deleted file mode 100644 index 537d583c58..0000000000 Binary files a/frontend/src/components/UserIcon/workspace.png and /dev/null differ diff --git a/frontend/src/components/UserIcon/workspace.svg b/frontend/src/components/UserIcon/workspace.svg new file mode 100644 index 0000000000..6a24de58bd --- /dev/null +++ b/frontend/src/components/UserIcon/workspace.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx index 27025b0526..f1d7bbe1d1 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx @@ -76,7 +76,7 @@ const HistoricalMessage = ({ role === "user" ? USER_BACKGROUND_COLOR : AI_BACKGROUND_COLOR }`} > -
+
@@ -98,9 +98,9 @@ const HistoricalMessage = ({ saveChanges={saveEditedMessage} /> ) : ( -
+
+

+ You can get a free API key{" "} + + from SearchApi. + +

+
+
+ + +
+
+ + + {/* */} +
+
+ + ); +} + export function SerperDotDevOptions({ settings }) { return ( <> diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/searchapi.png b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/searchapi.png new file mode 100644 index 0000000000..65bae79bf4 Binary files /dev/null and b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/searchapi.png differ diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx index c1f14cc6ae..fd201fb90c 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import GoogleSearchIcon from "./icons/google.png"; +import SearchApiIcon from "./icons/searchapi.png"; import SerperDotDevIcon from "./icons/serper.png"; import BingSearchIcon from "./icons/bing.png"; import SerplySearchIcon from "./icons/serply.png"; @@ -14,6 +15,7 @@ import { import SearchProviderItem from "./SearchProviderItem"; import WebSearchImage from "@/media/agents/scrape-websites.png"; import { + SearchApiOptions, SerperDotDevOptions, GoogleSearchOptions, BingSearchOptions, @@ -38,6 +40,14 @@ const SEARCH_PROVIDERS = [ description: "Web search powered by a custom Google Search Engine. Free for 100 queries per day.", }, + { + name: "SearchApi", + value: "searchapi", + logo: SearchApiIcon, + options: (settings) => , + description: + "SearchApi delivers structured data from multiple search engines. Free for 100 queries, but then paid. ", + }, { name: "Serper.dev", value: "serper-dot-dev", diff --git a/frontend/src/pages/Admin/System/index.jsx b/frontend/src/pages/Admin/System/index.jsx index bd7031550b..dba1872d3e 100644 --- a/frontend/src/pages/Admin/System/index.jsx +++ b/frontend/src/pages/Admin/System/index.jsx @@ -115,7 +115,6 @@ export default function AdminSystem() { }} value={messageLimit.limit} min={1} - max={300} className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-60 p-2.5" />
diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 4a56e4f92c..475910ac4d 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -483,14 +483,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@metamask/jazzicon@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@metamask/jazzicon/-/jazzicon-2.0.0.tgz#5615528e91c0fc5c9d79202d1f0954a7922525a0" - integrity sha512-7M+WSZWKcQAo0LEhErKf1z+D3YX0tEDAcGvcKbDyvDg34uvgeKR00mFNIYwAhdAS9t8YXxhxZgsrRBBg6X8UQg== - dependencies: - color "^0.11.3" - mersenne-twister "^1.1.0" - "@microsoft/fetch-event-source@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d" @@ -1097,11 +1089,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - clsx@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" @@ -1112,7 +1099,7 @@ clsx@^2.0.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== -color-convert@^1.3.0, color-convert@^1.9.0: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -1131,27 +1118,11 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" - integrity sha512-sz29j1bmSDfoAxKIEU6zwoIZXN6BrFbAMIhfYCNyiZXBDuU/aiHlN84lp/xDzL2ubyFhLDobHIlU1X70XRrMDA== - dependencies: - color-name "^1.0.0" - -color@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" - integrity sha512-Ajpjd8asqZ6EdxQeqGzU5WBhhTfJ/0cA4Wlbre7e5vXfmDSmda7Ov6jeKoru+b0vHcb1CqvuroTHp5zIWzhVMA== - dependencies: - clone "^1.0.2" - color-convert "^1.3.0" - color-string "^0.3.0" - commander@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -2545,11 +2516,6 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -mersenne-twister@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" - integrity sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA== - micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.7" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" diff --git a/server/.env.example b/server/.env.example index 22bd557eec..f942d6832a 100644 --- a/server/.env.example +++ b/server/.env.example @@ -241,6 +241,10 @@ TTS_PROVIDER="native" # AGENT_GSE_KEY= # AGENT_GSE_CTX= +#------ SearchApi.io ----------- https://www.searchapi.io/ +# AGENT_SEARCHAPI_API_KEY= +# AGENT_SEARCHAPI_ENGINE=google + #------ Serper.dev ----------- https://serper.dev/ # AGENT_SERPER_DEV_KEY= diff --git a/server/endpoints/api/admin/index.js b/server/endpoints/api/admin/index.js index e1eb05450a..11c9d1c116 100644 --- a/server/endpoints/api/admin/index.js +++ b/server/endpoints/api/admin/index.js @@ -88,7 +88,6 @@ function apiAdminEndpoints(app) { #swagger.requestBody = { description: 'Key pair object that will define the new user to add to the system.', required: true, - type: 'object', content: { "application/json": { example: { @@ -153,7 +152,6 @@ function apiAdminEndpoints(app) { #swagger.requestBody = { description: 'Key pair object that will update the found user. All fields are optional and will not update unless specified.', required: true, - type: 'object', content: { "application/json": { example: { @@ -321,7 +319,6 @@ function apiAdminEndpoints(app) { #swagger.requestBody = { description: 'Request body for creation parameters of the invitation', required: false, - type: 'object', content: { "application/json": { example: { @@ -493,7 +490,6 @@ function apiAdminEndpoints(app) { #swagger.requestBody = { description: 'Entire array of user ids who can access the workspace. All fields are optional and will not update unless specified.', required: true, - type: 'object', content: { "application/json": { example: { @@ -553,7 +549,6 @@ function apiAdminEndpoints(app) { #swagger.requestBody = { description: 'Page offset to show of workspace chats. All fields are optional and will not update unless specified.', required: false, - type: 'integer', content: { "application/json": { example: { @@ -660,7 +655,6 @@ function apiAdminEndpoints(app) { #swagger.requestBody = { description: 'Object with setting key and new value to set. All keys are optional and will not update unless specified.', required: true, - type: 'object', content: { "application/json": { example: { diff --git a/server/endpoints/api/document/index.js b/server/endpoints/api/document/index.js index b4461175ad..a646fb584a 100644 --- a/server/endpoints/api/document/index.js +++ b/server/endpoints/api/document/index.js @@ -31,11 +31,11 @@ function apiDocumentEndpoints(app) { #swagger.requestBody = { description: 'File to be uploaded.', required: true, - type: 'file', content: { "multipart/form-data": { schema: { - type: 'object', + type: 'string', + format: 'binary', properties: { file: { type: 'string', @@ -131,7 +131,6 @@ function apiDocumentEndpoints(app) { #swagger.requestBody = { description: 'Link of web address to be scraped.', required: true, - type: 'object', content: { "application/json": { schema: { @@ -229,7 +228,6 @@ function apiDocumentEndpoints(app) { #swagger.requestBody = { description: 'Text content and metadata of the file to be saved to the system. Use metadata-schema endpoint to get the possible metadata keys', required: true, - type: 'object', content: { "application/json": { schema: { @@ -571,11 +569,10 @@ function apiDocumentEndpoints(app) { #swagger.requestBody = { description: 'Name of the folder to create.', required: true, - type: 'object', content: { "application/json": { schema: { - type: 'object', + type: 'string', example: { "name": "new-folder" } @@ -638,7 +635,6 @@ function apiDocumentEndpoints(app) { #swagger.requestBody = { description: 'Array of objects containing source and destination paths of files to move.', required: true, - type: 'object', content: { "application/json": { schema: { diff --git a/server/endpoints/api/openai/index.js b/server/endpoints/api/openai/index.js index cd732f424a..1f805359f8 100644 --- a/server/endpoints/api/openai/index.js +++ b/server/endpoints/api/openai/index.js @@ -90,7 +90,6 @@ function apiOpenAICompatibleEndpoints(app) { #swagger.requestBody = { description: 'Send a prompt to the workspace with full use of documents as if sending a chat in AnythingLLM. Only supports some values of OpenAI API. See example below.', required: true, - type: 'object', content: { "application/json": { example: { @@ -205,7 +204,6 @@ function apiOpenAICompatibleEndpoints(app) { #swagger.requestBody = { description: 'The input string(s) to be embedded. If the text is too long for the embedder model context, it will fail to embed. The vector and associated chunk metadata will be returned in the array order provided', required: true, - type: 'object', content: { "application/json": { example: { diff --git a/server/endpoints/api/system/index.js b/server/endpoints/api/system/index.js index fa92a7c3bd..f6b9f511df 100644 --- a/server/endpoints/api/system/index.js +++ b/server/endpoints/api/system/index.js @@ -113,7 +113,6 @@ function apiSystemEndpoints(app) { #swagger.requestBody = { description: 'Key pair object that matches a valid setting and value. Get keys from GET /v1/system or refer to codebase.', required: true, - type: 'object', content: { "application/json": { example: { diff --git a/server/endpoints/api/workspace/index.js b/server/endpoints/api/workspace/index.js index 694baea982..d2040be1e4 100644 --- a/server/endpoints/api/workspace/index.js +++ b/server/endpoints/api/workspace/index.js @@ -25,7 +25,6 @@ function apiWorkspaceEndpoints(app) { #swagger.requestBody = { description: 'JSON object containing new display name of workspace.', required: true, - type: 'object', content: { "application/json": { example: { @@ -265,7 +264,6 @@ function apiWorkspaceEndpoints(app) { #swagger.requestBody = { description: 'JSON object containing new settings to update a workspace. All keys are optional and will not update unless provided', required: true, - type: 'object', content: { "application/json": { example: { @@ -404,7 +402,6 @@ function apiWorkspaceEndpoints(app) { #swagger.requestBody = { description: 'JSON object of additions and removals of documents to add to update a workspace. The value should be the folder + filename with the exclusions of the top-level documents path.', required: true, - type: 'object', content: { "application/json": { example: { @@ -482,7 +479,6 @@ function apiWorkspaceEndpoints(app) { #swagger.requestBody = { description: 'JSON object with the document path and pin status to update.', required: true, - type: 'object', content: { "application/json": { example: { @@ -545,7 +541,6 @@ function apiWorkspaceEndpoints(app) { #swagger.requestBody = { description: 'Send a prompt to the workspace and the type of conversation (query or chat).
Query: Will not use LLM unless there are relevant sources from vectorDB & does not recall chat history.
Chat: Uses LLM general knowledge w/custom embeddings to produce output, uses rolling chat history.', required: true, - type: 'object', content: { "application/json": { example: { @@ -655,7 +650,6 @@ function apiWorkspaceEndpoints(app) { #swagger.requestBody = { description: 'Send a prompt to the workspace and the type of conversation (query or chat).
Query: Will not use LLM unless there are relevant sources from vectorDB & does not recall chat history.
Chat: Uses LLM general knowledge w/custom embeddings to produce output, uses rolling chat history.', required: true, - type: 'object', content: { "application/json": { example: { @@ -671,6 +665,9 @@ function apiWorkspaceEndpoints(app) { "text/event-stream": { schema: { type: 'array', + items: { + type: 'string', + }, example: [ { id: 'uuid-123', diff --git a/server/endpoints/api/workspaceThread/index.js b/server/endpoints/api/workspaceThread/index.js index f8552d73c1..66c3a68445 100644 --- a/server/endpoints/api/workspaceThread/index.js +++ b/server/endpoints/api/workspaceThread/index.js @@ -36,7 +36,6 @@ function apiWorkspaceThreadEndpoints(app) { #swagger.requestBody = { description: 'Optional userId associated with the thread', required: false, - type: 'object', content: { "application/json": { example: { @@ -125,7 +124,6 @@ function apiWorkspaceThreadEndpoints(app) { #swagger.requestBody = { description: 'JSON object containing new name to update the thread.', required: true, - type: 'object', content: { "application/json": { example: { @@ -336,7 +334,6 @@ function apiWorkspaceThreadEndpoints(app) { #swagger.requestBody = { description: 'Send a prompt to the workspace thread and the type of conversation (query or chat).', required: true, - type: 'object', content: { "application/json": { example: { @@ -462,7 +459,6 @@ function apiWorkspaceThreadEndpoints(app) { #swagger.requestBody = { description: 'Send a prompt to the workspace thread and the type of conversation (query or chat).', required: true, - type: 'object', content: { "application/json": { example: { @@ -478,6 +474,9 @@ function apiWorkspaceThreadEndpoints(app) { "text/event-stream": { schema: { type: 'array', + items: { + type: 'string', + }, example: [ { id: 'uuid-123', diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 712349b9d6..c2c03ffa09 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -98,6 +98,7 @@ const SystemSettings = { if ( ![ "google-search-engine", + "searchapi", "serper-dot-dev", "bing-search", "serply-engine", @@ -235,6 +236,8 @@ const SystemSettings = { // -------------------------------------------------------- AgentGoogleSearchEngineId: process.env.AGENT_GSE_CTX || null, AgentGoogleSearchEngineKey: !!process.env.AGENT_GSE_KEY || null, + AgentSearchApiKey: !!process.env.AGENT_SEARCHAPI_API_KEY || null, + AgentSearchApiEngine: process.env.AGENT_SEARCHAPI_ENGINE || "google", AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null, AgentBingSearchApiKey: !!process.env.AGENT_BING_SEARCH_API_KEY || null, AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null, diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index 078e38a884..f412f0a4f7 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -193,7 +193,6 @@ "requestBody": { "description": "Key pair object that will define the new user to add to the system.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -263,7 +262,6 @@ "requestBody": { "description": "Key pair object that will update the found user. All fields are optional and will not update unless specified.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -434,7 +432,6 @@ "requestBody": { "description": "Request body for creation parameters of the invitation", "required": false, - "type": "object", "content": { "application/json": { "example": { @@ -626,7 +623,6 @@ "requestBody": { "description": "Entire array of user ids who can access the workspace. All fields are optional and will not update unless specified.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -686,7 +682,6 @@ "requestBody": { "description": "Page offset to show of workspace chats. All fields are optional and will not update unless specified.", "required": false, - "type": "integer", "content": { "application/json": { "example": { @@ -790,7 +785,6 @@ "requestBody": { "description": "Object with setting key and new value to set. All keys are optional and will not update unless specified.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -861,11 +855,11 @@ "requestBody": { "description": "File to be uploaded.", "required": true, - "type": "file", "content": { "multipart/form-data": { "schema": { - "type": "object", + "type": "string", + "format": "binary", "properties": { "file": { "type": "string", @@ -938,7 +932,6 @@ "requestBody": { "description": "Link of web address to be scraped.", "required": true, - "type": "object", "content": { "application/json": { "schema": { @@ -1015,7 +1008,6 @@ "requestBody": { "description": "Text content and metadata of the file to be saved to the system. Use metadata-schema endpoint to get the possible metadata keys", "required": true, - "type": "object", "content": { "application/json": { "schema": { @@ -1311,11 +1303,10 @@ "requestBody": { "description": "Name of the folder to create.", "required": true, - "type": "object", "content": { "application/json": { "schema": { - "type": "object", + "type": "string", "example": { "name": "new-folder" } @@ -1369,7 +1360,6 @@ "requestBody": { "description": "Array of objects containing source and destination paths of files to move.", "required": true, - "type": "object", "content": { "application/json": { "schema": { @@ -1441,7 +1431,6 @@ "requestBody": { "description": "JSON object containing new display name of workspace.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -1679,7 +1668,6 @@ "requestBody": { "description": "JSON object containing new settings to update a workspace. All keys are optional and will not update unless provided", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -1830,7 +1818,6 @@ "requestBody": { "description": "JSON object of additions and removals of documents to add to update a workspace. The value should be the folder + filename with the exclusions of the top-level documents path.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -1890,7 +1877,6 @@ "requestBody": { "description": "JSON object with the document path and pin status to update.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -1967,7 +1953,6 @@ "requestBody": { "description": "Send a prompt to the workspace and the type of conversation (query or chat).
Query: Will not use LLM unless there are relevant sources from vectorDB & does not recall chat history.
Chat: Uses LLM general knowledge w/custom embeddings to produce output, uses rolling chat history.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -2002,6 +1987,9 @@ "text/event-stream": { "schema": { "type": "array", + "items": { + "type": "string" + }, "example": [ { "id": "uuid-123", @@ -2060,7 +2048,6 @@ "requestBody": { "description": "Send a prompt to the workspace and the type of conversation (query or chat).
Query: Will not use LLM unless there are relevant sources from vectorDB & does not recall chat history.
Chat: Uses LLM general knowledge w/custom embeddings to produce output, uses rolling chat history.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -2241,7 +2228,6 @@ "requestBody": { "description": "Key pair object that matches a valid setting and value. Get keys from GET /v1/system or refer to codebase.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -2440,7 +2426,6 @@ "requestBody": { "description": "Optional userId associated with the thread", "required": false, - "type": "object", "content": { "application/json": { "example": { @@ -2523,7 +2508,6 @@ "requestBody": { "description": "JSON object containing new name to update the thread.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -2742,7 +2726,6 @@ "requestBody": { "description": "Send a prompt to the workspace thread and the type of conversation (query or chat).", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -2787,6 +2770,9 @@ "text/event-stream": { "schema": { "type": "array", + "items": { + "type": "string" + }, "example": [ { "id": "uuid-123", @@ -2845,7 +2831,6 @@ "requestBody": { "description": "Send a prompt to the workspace thread and the type of conversation (query or chat).", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -3012,7 +2997,6 @@ "requestBody": { "description": "Send a prompt to the workspace with full use of documents as if sending a chat in AnythingLLM. Only supports some values of OpenAI API. See example below.", "required": true, - "type": "object", "content": { "application/json": { "example": { @@ -3076,7 +3060,6 @@ "requestBody": { "description": "The input string(s) to be embedded. If the text is too long for the embedder model context, it will fail to embed. The vector and associated chunk metadata will be returned in the array order provided", "required": true, - "type": "object", "content": { "application/json": { "example": { diff --git a/server/utils/EmbeddingEngines/ollama/index.js b/server/utils/EmbeddingEngines/ollama/index.js index 2d1cea7ae1..8f7239c7e0 100644 --- a/server/utils/EmbeddingEngines/ollama/index.js +++ b/server/utils/EmbeddingEngines/ollama/index.js @@ -36,67 +36,48 @@ class OllamaEmbedder { return result?.[0] || []; } + /** + * This function takes an array of text chunks and embeds them using the Ollama API. + * chunks are processed sequentially to avoid overwhelming the API with too many requests + * or running out of resources on the endpoint running the ollama instance. + * @param {string[]} textChunks - An array of text chunks to embed. + * @returns {Promise>} - A promise that resolves to an array of embeddings. + */ async embedChunks(textChunks = []) { if (!(await this.#isAlive())) throw new Error( `Ollama service could not be reached. Is Ollama running?` ); - const embeddingRequests = []; this.log( `Embedding ${textChunks.length} chunks of text with ${this.model}.` ); - for (const chunk of textChunks) { - embeddingRequests.push( - new Promise((resolve) => { - fetch(this.basePath, { - method: "POST", - body: JSON.stringify({ - model: this.model, - prompt: chunk, - }), - }) - .then((res) => res.json()) - .then(({ embedding }) => { - resolve({ data: embedding, error: null }); - return; - }) - .catch((error) => { - resolve({ data: [], error: error.message }); - return; - }); - }) - ); - } + let data = []; + let error = null; - const { data = [], error = null } = await Promise.all( - embeddingRequests - ).then((results) => { - // If any errors were returned from Ollama abort the entire sequence because the embeddings - // will be incomplete. + for (const chunk of textChunks) { + try { + const res = await fetch(this.basePath, { + method: "POST", + body: JSON.stringify({ + model: this.model, + prompt: chunk, + }), + }); - const errors = results - .filter((res) => !!res.error) - .map((res) => res.error) - .flat(); - if (errors.length > 0) { - let uniqueErrors = new Set(); - errors.map((error) => - uniqueErrors.add(`[${error.type}]: ${error.message}`) - ); + const { embedding } = await res.json(); + if (!Array.isArray(embedding) || embedding.length === 0) + throw new Error("Ollama returned an empty embedding for chunk!"); - return { - data: [], - error: Array.from(uniqueErrors).join(", "), - }; + data.push(embedding); + } catch (err) { + this.log(err.message); + error = err.message; + data = []; + break; } - - return { - data: results.map((res) => res?.data || []), - error: null, - }; - }); + } if (!!error) throw new Error(`Ollama Failed to embed: ${error}`); return data.length > 0 ? data : null; diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js index f4269fe139..76849056ee 100644 --- a/server/utils/agents/aibitat/plugins/web-browsing.js +++ b/server/utils/agents/aibitat/plugins/web-browsing.js @@ -62,6 +62,9 @@ const webBrowsing = { case "google-search-engine": engine = "_googleSearchEngine"; break; + case "searchapi": + engine = "_searchApi"; + break; case "serper-dot-dev": engine = "_serperDotDev"; break; @@ -130,6 +133,72 @@ const webBrowsing = { return JSON.stringify(data); }, + /** + * Use SearchApi + * SearchApi supports multiple search engines like Google Search, Bing Search, Baidu Search, Google News, YouTube, and many more. + * https://www.searchapi.io/ + */ + _searchApi: async function (query) { + if (!process.env.AGENT_SEARCHAPI_API_KEY) { + this.super.introspect( + `${this.caller}: I can't use SearchApi searching because the user has not defined the required API key.\nVisit: https://www.searchapi.io/ to create the API key for free.` + ); + return `Search is disabled and no content was found. This functionality is disabled because the user has not set it up yet.`; + } + + this.super.introspect( + `${this.caller}: Using SearchApi to search for "${ + query.length > 100 ? `${query.slice(0, 100)}...` : query + }"` + ); + + const engine = process.env.AGENT_SEARCHAPI_ENGINE; + const params = new URLSearchParams({ + engine: engine, + q: query, + }); + + const url = `https://www.searchapi.io/api/v1/search?${params.toString()}`; + const { response, error } = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${process.env.AGENT_SEARCHAPI_API_KEY}`, + "Content-Type": "application/json", + "X-SearchApi-Source": "AnythingLLM", + }, + }) + .then((res) => res.json()) + .then((data) => { + return { response: data, error: null }; + }) + .catch((e) => { + return { response: null, error: e.message }; + }); + if (error) + return `There was an error searching for content. ${error}`; + + const data = []; + if (response.hasOwnProperty("knowledge_graph")) + data.push(response.knowledge_graph?.description); + if (response.hasOwnProperty("answer_box")) + data.push(response.answer_box?.answer); + response.organic_results?.forEach((searchResult) => { + const { title, link, snippet } = searchResult; + data.push({ + title, + link, + snippet, + }); + }); + + if (data.length === 0) + return `No information was found online for the search query.`; + this.super.introspect( + `${this.caller}: I found ${data.length} results - looking over them now.` + ); + return JSON.stringify(data); + }, + /** * Use Serper.dev * Free to set up, easy to use, 2,500 calls for free one-time diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index c579da188e..af5a460dbc 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -435,6 +435,14 @@ const KEY_MAPPING = { envKey: "AGENT_GSE_KEY", checks: [], }, + AgentSearchApiKey: { + envKey: "AGENT_SEARCHAPI_API_KEY", + checks: [], + }, + AgentSearchApiEngine: { + envKey: "AGENT_SEARCHAPI_ENGINE", + checks: [], + }, AgentSerperApiKey: { envKey: "AGENT_SERPER_DEV_KEY", checks: [],