@@ -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: [],