From 88ff32ebe93c31728166fd69737585de7555eb19 Mon Sep 17 00:00:00 2001 From: bdqfork Date: Sat, 24 Feb 2024 00:32:34 +0800 Subject: [PATCH] support set memory from ui --- libs/superagent/Dockerfile | 4 +- libs/superagent/app/agents/base.py | 7 +- libs/superagent/app/agents/langchain.py | 37 ++- libs/superagent/app/api/agents.py | 5 + libs/superagent/app/api/memory_dbs.py | 104 +++++++++ libs/superagent/app/main.py | 3 +- libs/superagent/app/models/request.py | 8 +- libs/superagent/app/models/response.py | 13 ++ libs/superagent/app/routers.py | 2 + libs/superagent/app/workflows/base.py | 14 +- .../migration.sql | 20 ++ libs/superagent/prisma/schema.prisma | 17 ++ libs/ui/app/agents/[agentId]/settings.tsx | 43 ++++ libs/ui/app/integrations/client-page.tsx | 9 + libs/ui/app/integrations/memory.tsx | 218 ++++++++++++++++++ libs/ui/app/integrations/page.tsx | 13 +- libs/ui/config/site.ts | 50 +++- libs/ui/lib/api.ts | 18 ++ libs/ui/models/models.ts | 5 + libs/ui/public/75705874.png:Zone.Identifier | 4 + libs/ui/public/motorhead.png | Bin 0 -> 1612 bytes libs/ui/public/redis.png | Bin 0 -> 8975 bytes libs/ui/types/agent.ts | 1 + 23 files changed, 575 insertions(+), 20 deletions(-) create mode 100644 libs/superagent/app/api/memory_dbs.py create mode 100644 libs/superagent/prisma/migrations/20240223164205_add_memory_db/migration.sql create mode 100644 libs/ui/app/integrations/memory.tsx create mode 100644 libs/ui/public/75705874.png:Zone.Identifier create mode 100644 libs/ui/public/motorhead.png create mode 100644 libs/ui/public/redis.png diff --git a/libs/superagent/Dockerfile b/libs/superagent/Dockerfile index 6aaf1e365..8fa599a28 100644 --- a/libs/superagent/Dockerfile +++ b/libs/superagent/Dockerfile @@ -31,11 +31,11 @@ ENV PORT="8080" COPY --from=builder /app/.venv /app/.venv -COPY . ./ - # Improve grpc error messages RUN pip install grpcio-status +COPY . ./ + # Enable prisma migrations RUN prisma generate diff --git a/libs/superagent/app/agents/base.py b/libs/superagent/app/agents/base.py index 0e183fab8..7d4355777 100644 --- a/libs/superagent/app/agents/base.py +++ b/libs/superagent/app/agents/base.py @@ -3,7 +3,7 @@ from app.models.request import LLMParams from app.utils.callbacks import CustomAsyncIteratorCallbackHandler from prisma.enums import AgentType -from prisma.models import Agent +from prisma.models import Agent, MemoryDb DEFAULT_PROMPT = ( "You are a helpful AI Assistant, answer the users questions to " @@ -21,6 +21,7 @@ def __init__( callbacks: List[CustomAsyncIteratorCallbackHandler] = [], llm_params: Optional[LLMParams] = {}, agent_config: Agent = None, + memory_config: MemoryDb = None, ): self.agent_id = agent_id self.session_id = session_id @@ -29,6 +30,7 @@ def __init__( self.callbacks = callbacks self.llm_params = llm_params self.agent_config = agent_config + self.memory_config = memory_config async def _get_tools( self, @@ -60,6 +62,7 @@ async def get_agent(self): callbacks=self.callbacks, llm_params=self.llm_params, agent_config=self.agent_config, + memory_config=self.memory_config, ) elif self.agent_config.type == AgentType.LLM: @@ -72,6 +75,7 @@ async def get_agent(self): callbacks=self.callbacks, llm_params=self.llm_params, agent_config=self.agent_config, + memory_config=self.memory_config, ) else: @@ -85,6 +89,7 @@ async def get_agent(self): callbacks=self.callbacks, llm_params=self.llm_params, agent_config=self.agent_config, + memory_config=self.memory_config, ) return await agent.get_agent() diff --git a/libs/superagent/app/agents/langchain.py b/libs/superagent/app/agents/langchain.py index 805c7af62..e198869ec 100644 --- a/libs/superagent/app/agents/langchain.py +++ b/libs/superagent/app/agents/langchain.py @@ -1,5 +1,6 @@ import datetime import json +import logging import re from typing import Any, List @@ -23,8 +24,11 @@ from app.models.tools import DatasourceInput from app.tools import TOOL_TYPE_MAPPING, create_pydantic_model_from_object, create_tool from app.tools.datasource import DatasourceTool, StructuredDatasourceTool +from app.utils.helpers import get_first_non_null from app.utils.llm import LLM_MAPPING -from prisma.models import LLM, Agent, AgentDatasource, AgentTool +from prisma.models import LLM, Agent, AgentDatasource, AgentTool, MemoryDb + +logger = logging.getLogger(__name__) DEFAULT_PROMPT = ( "You are a helpful AI Assistant, answer the users questions to " @@ -193,9 +197,15 @@ async def _get_prompt(self, agent: Agent) -> str: content = f"{content}" f"\n\n{datetime.datetime.now().strftime('%Y-%m-%d')}" return SystemMessage(content=content) - async def _get_memory(self) -> List: - memory_type = config("MEMORY", "motorhead") - if memory_type == "redis": + async def _get_memory(self, memory_db: MemoryDb) -> List: + logger.debug(f"Use memory config: {memory_db}") + if memory_db is None: + memory_provider = config("MEMORY") + options = {} + else: + memory_provider = memory_db.provider + options = memory_db.options + if memory_provider == "REDIS" or memory_provider == "redis": memory = ConversationBufferWindowMemory( chat_memory=RedisChatMessageHistory( session_id=( @@ -203,15 +213,21 @@ async def _get_memory(self) -> List: if self.session_id else f"{self.agent_id}" ), - url=config("REDIS_MEMORY_URL", "redis://localhost:6379/0"), + url=get_first_non_null( + options.get("REDIS_MEMORY_URL"), + config("REDIS_MEMORY_URL", "redis://localhost:6379/0"), + ), key_prefix="superagent:", ), memory_key="chat_history", return_messages=True, output_key="output", - k=config("REDIS_MEMORY_WINDOW", 10), + k=get_first_non_null( + options.get("REDIS_MEMORY_WINDOW"), + config("REDIS_MEMORY_WINDOW", 10), + ), ) - else: + elif memory_provider == "MOTORHEAD" or memory_provider == "motorhead": memory = MotorheadMemory( session_id=( f"{self.agent_id}-{self.session_id}" @@ -219,7 +235,10 @@ async def _get_memory(self) -> List: else f"{self.agent_id}" ), memory_key="chat_history", - url=config("MEMORY_API_URL"), + url=get_first_non_null( + options.get("MEMORY_API_URL"), + config("MEMORY_API_URL"), + ), return_messages=True, output_key="output", ) @@ -235,7 +254,7 @@ async def get_agent(self): agent_tools=self.agent_config.tools, ) prompt = await self._get_prompt(agent=self.agent_config) - memory = await self._get_memory() + memory = await self._get_memory(memory_db=self.memory_config) if len(tools) > 0: agent = initialize_agent( diff --git a/libs/superagent/app/api/agents.py b/libs/superagent/app/api/agents.py index fe6124324..e4661295a 100644 --- a/libs/superagent/app/api/agents.py +++ b/libs/superagent/app/api/agents.py @@ -452,6 +452,10 @@ async def invoke( if not model and metadata.get("model"): model = metadata.get("model") + memory_config = await prisma.memorydb.find_first( + where={"provider": agent_config.memory, "apiUserId": api_user.id}, + ) + def track_agent_invocation(result): intermediate_steps_to_obj = [ { @@ -571,6 +575,7 @@ async def send_message( callbacks=monitoring_callbacks, llm_params=body.llm_params, agent_config=agent_config, + memory_config=memory_config, ) agent = await agent_base.get_agent() diff --git a/libs/superagent/app/api/memory_dbs.py b/libs/superagent/app/api/memory_dbs.py new file mode 100644 index 000000000..57b30af89 --- /dev/null +++ b/libs/superagent/app/api/memory_dbs.py @@ -0,0 +1,104 @@ +import json + +import segment.analytics as analytics +from decouple import config +from fastapi import APIRouter, Depends + +from app.models.request import MemoryDb as MemoryDbRequest +from app.models.response import MemoryDb as MemoryDbResponse +from app.models.response import MemoryDbList as MemoryDbListResponse +from app.utils.api import get_current_api_user, handle_exception +from app.utils.prisma import prisma +from prisma import Json + +SEGMENT_WRITE_KEY = config("SEGMENT_WRITE_KEY", None) + +router = APIRouter() +analytics.write_key = SEGMENT_WRITE_KEY + + +@router.post( + "/memory-db", + name="create", + description="Create a new Memory Database", + response_model=MemoryDbResponse, +) +async def create(body: MemoryDbRequest, api_user=Depends(get_current_api_user)): + """Endpoint for creating a Memory Database""" + if SEGMENT_WRITE_KEY: + analytics.track(api_user.id, "Created Memory Database") + + data = await prisma.memorydb.create( + { + **body.dict(), + "apiUserId": api_user.id, + "options": json.dumps(body.options), + } + ) + data.options = json.dumps(data.options) + return {"success": True, "data": data} + + +@router.get( + "/memory-dbs", + name="list", + description="List all Memory Databases", + response_model=MemoryDbListResponse, +) +async def list(api_user=Depends(get_current_api_user)): + """Endpoint for listing all Memory Databases""" + try: + data = await prisma.memorydb.find_many( + where={"apiUserId": api_user.id}, order={"createdAt": "desc"} + ) + # Convert options to string + for item in data: + item.options = json.dumps(item.options) + return {"success": True, "data": data} + except Exception as e: + handle_exception(e) + + +@router.get( + "/memory-dbs/{memory_db_id}", + name="get", + description="Get a single Memory Database", + response_model=MemoryDbResponse, +) +async def get(memory_db_id: str, api_user=Depends(get_current_api_user)): + """Endpoint for getting a single Memory Database""" + try: + data = await prisma.memorydb.find_first( + where={"id": memory_db_id, "apiUserId": api_user.id} + ) + data.options = json.dumps(data.options) + return {"success": True, "data": data} + except Exception as e: + handle_exception(e) + + +@router.patch( + "/memory-dbs/{memory_db_id}", + name="update", + description="Patch a Memory Database", + response_model=MemoryDbResponse, +) +async def update( + memory_db_id: str, body: MemoryDbRequest, api_user=Depends(get_current_api_user) +): + """Endpoint for patching a Memory Database""" + try: + if SEGMENT_WRITE_KEY: + analytics.track(api_user.id, "Updated Memory Database") + data = await prisma.memorydb.update( + where={"id": memory_db_id}, + data={ + **body.dict(exclude_unset=True), + "apiUserId": api_user.id, + "options": Json(body.options), + }, + ) + data.options = json.dumps(data.options) + return {"success": True, "data": data} + except Exception as e: + handle_exception(e) diff --git a/libs/superagent/app/main.py b/libs/superagent/app/main.py index b2288d4af..17645bfe2 100644 --- a/libs/superagent/app/main.py +++ b/libs/superagent/app/main.py @@ -1,4 +1,5 @@ import logging +import os import time import colorlog @@ -26,7 +27,7 @@ console_handler.setFormatter(formatter) logging.basicConfig( - level=logging.INFO, + level=os.environ.get("LOG_LEVEL", "INFO"), format="%(levelname)s: %(message)s", handlers=[console_handler], force=True, diff --git a/libs/superagent/app/models/request.py b/libs/superagent/app/models/request.py index 6665e5858..91808c988 100644 --- a/libs/superagent/app/models/request.py +++ b/libs/superagent/app/models/request.py @@ -3,7 +3,7 @@ from openai.types.beta.assistant_create_params import Tool as OpenAiAssistantTool from pydantic import BaseModel -from prisma.enums import AgentType, LLMProvider, VectorDbProvider +from prisma.enums import AgentType, LLMProvider, MemoryDbProvider, VectorDbProvider class ApiUser(BaseModel): @@ -40,6 +40,7 @@ class AgentUpdate(BaseModel): initialMessage: Optional[str] prompt: Optional[str] llmModel: Optional[str] + memory: Optional[str] description: Optional[str] avatar: Optional[str] type: Optional[str] @@ -132,3 +133,8 @@ class WorkflowInvoke(BaseModel): class VectorDb(BaseModel): provider: VectorDbProvider options: Dict + + +class MemoryDb(BaseModel): + provider: MemoryDbProvider + options: Dict diff --git a/libs/superagent/app/models/response.py b/libs/superagent/app/models/response.py index c17c2b0db..8c7dd2bec 100644 --- a/libs/superagent/app/models/response.py +++ b/libs/superagent/app/models/response.py @@ -20,6 +20,9 @@ from prisma.models import ( Datasource as DatasourceModel, ) +from prisma.models import ( + MemoryDb as MemoryDbModel, +) from prisma.models import ( Tool as ToolModel, ) @@ -141,3 +144,13 @@ class VectorDb(BaseModel): class VectorDbList(BaseModel): success: bool data: Optional[List[VectorDbModel]] + + +class MemoryDb(BaseModel): + success: bool + data: Optional[MemoryDbModel] + + +class MemoryDbList(BaseModel): + success: bool + data: Optional[List[MemoryDbModel]] diff --git a/libs/superagent/app/routers.py b/libs/superagent/app/routers.py index 1ea4b2f2e..2a99aeeee 100644 --- a/libs/superagent/app/routers.py +++ b/libs/superagent/app/routers.py @@ -5,6 +5,7 @@ api_user, datasources, llms, + memory_dbs, tools, vector_dbs, workflows, @@ -24,3 +25,4 @@ workflow_configs.router, tags=["Workflow Config"], prefix=api_prefix ) router.include_router(vector_dbs.router, tags=["Vector Database"], prefix=api_prefix) +router.include_router(memory_dbs.router, tags=["Memory Database"], prefix=api_prefix) diff --git a/libs/superagent/app/workflows/base.py b/libs/superagent/app/workflows/base.py index 607906f9c..8929f5284 100644 --- a/libs/superagent/app/workflows/base.py +++ b/libs/superagent/app/workflows/base.py @@ -1,3 +1,4 @@ +import logging from typing import Any, List from agentops.langchain_callback_handler import ( @@ -10,6 +11,8 @@ from app.utils.prisma import prisma from prisma.models import Workflow +logger = logging.getLogger(__name__) + class WorkflowBase: def __init__( @@ -43,12 +46,19 @@ async def arun(self, input: Any): "tools": {"include": {"tool": True}}, }, ) + memory_config = await prisma.memorydb.find_first( + where={ + "provider": agent_config.memory, + "apiUserId": agent_config.apiUserId, + }, + ) agent_base = AgentBase( agent_id=step.agentId, enable_streaming=self.enable_streaming, callbacks=self.constructor_callbacks, session_id=self.session_id, agent_config=agent_config, + memory_config=memory_config, ) agent = await agent_base.get_agent() @@ -56,15 +66,15 @@ async def arun(self, input: Any): previous_output, agent_type=agent_config.type, ) - + logger.debug(f"Workflow step {stepIndex} start, input: {agent_input}") agent_response = await agent.ainvoke( input=agent_input, config={ "callbacks": self.callbacks[stepIndex], }, ) - previous_output = agent_response.get("output") + logger.debug(f"Workflow step {stepIndex} stop, output: {previous_output}") steps_output.append(agent_response) return {"steps": steps_output, "output": previous_output} diff --git a/libs/superagent/prisma/migrations/20240223164205_add_memory_db/migration.sql b/libs/superagent/prisma/migrations/20240223164205_add_memory_db/migration.sql new file mode 100644 index 000000000..c6fbaab5f --- /dev/null +++ b/libs/superagent/prisma/migrations/20240223164205_add_memory_db/migration.sql @@ -0,0 +1,20 @@ +-- CreateEnum +CREATE TYPE "MemoryDbProvider" AS ENUM ('MOTORHEAD', 'REDIS'); + +-- AlterTable +ALTER TABLE "Agent" ADD COLUMN "memory" "MemoryDbProvider" DEFAULT 'MOTORHEAD'; + +-- CreateTable +CREATE TABLE "MemoryDb" ( + "id" TEXT NOT NULL, + "provider" "MemoryDbProvider" NOT NULL DEFAULT 'MOTORHEAD', + "options" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "apiUserId" TEXT NOT NULL, + + CONSTRAINT "MemoryDb_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "MemoryDb" ADD CONSTRAINT "MemoryDb_apiUserId_fkey" FOREIGN KEY ("apiUserId") REFERENCES "ApiUser"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/superagent/prisma/schema.prisma b/libs/superagent/prisma/schema.prisma index 538b7f762..6f3b7e9a2 100644 --- a/libs/superagent/prisma/schema.prisma +++ b/libs/superagent/prisma/schema.prisma @@ -93,6 +93,11 @@ enum VectorDbProvider { SUPABASE } +enum MemoryDbProvider { + MOTORHEAD + REDIS +} + model ApiUser { id String @id @default(uuid()) token String? @@ -106,6 +111,7 @@ model ApiUser { workflows Workflow[] vectorDb VectorDb[] workflowConfigs WorkflowConfig[] + MemoryDb MemoryDb[] } model Agent { @@ -120,6 +126,7 @@ model Agent { updatedAt DateTime @updatedAt llms AgentLLM[] llmModel LLMModel? @default(GPT_3_5_TURBO_16K_0613) + memory MemoryDbProvider? @default(MOTORHEAD) prompt String? apiUserId String apiUser ApiUser @relation(fields: [apiUserId], references: [id]) @@ -253,3 +260,13 @@ model VectorDb { apiUserId String apiUser ApiUser @relation(fields: [apiUserId], references: [id]) } + +model MemoryDb { + id String @id @default(uuid()) + provider MemoryDbProvider @default(MOTORHEAD) + options Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + apiUserId String + apiUser ApiUser @relation(fields: [apiUserId], references: [id]) +} diff --git a/libs/ui/app/agents/[agentId]/settings.tsx b/libs/ui/app/agents/[agentId]/settings.tsx index 2fdfb3ac3..ea5182ad8 100644 --- a/libs/ui/app/agents/[agentId]/settings.tsx +++ b/libs/ui/app/agents/[agentId]/settings.tsx @@ -46,6 +46,7 @@ const formSchema = z.object({ llms: z.string(), isActive: z.boolean().default(true), llmModel: z.string().nullable(), + memory: z.string().nullable(), prompt: z.string(), tools: z.array(z.string()), datasources: z.array(z.string()), @@ -92,6 +93,7 @@ export default function Settings({ tools: [], datasources: [], avatar: agent.avatar, + memory: agent.memory, }, }) const avatar = form.watch("avatar") @@ -304,6 +306,47 @@ export default function Settings({ )} +
+ Memory + {agent.memory.length > 0 ? ( +
+ ( + + + + + )} + /> +
+ ) : ( +
+

Heads up!

+

+ You need to add an Memory to this agent for it work. This can + be done through the SDK or API. +

+
+ )} +
diff --git a/libs/ui/app/integrations/client-page.tsx b/libs/ui/app/integrations/client-page.tsx index bd6e3c976..cd34030b6 100644 --- a/libs/ui/app/integrations/client-page.tsx +++ b/libs/ui/app/integrations/client-page.tsx @@ -3,16 +3,19 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import LLM from "./llm" +import Memory from "./memory" import Storage from "./storage" export default function IntegrationsClientPage({ profile, configuredDBs, configuredLLMs, + configuredMemories, }: { profile: any configuredDBs: any configuredLLMs: any + configuredMemories: any }) { return ( @@ -23,6 +26,9 @@ export default function IntegrationsClientPage({ LANGUAGE MODELS + + MEMORY + @@ -30,6 +36,9 @@ export default function IntegrationsClientPage({ + + + diff --git a/libs/ui/app/integrations/memory.tsx b/libs/ui/app/integrations/memory.tsx new file mode 100644 index 000000000..ecb1e0a60 --- /dev/null +++ b/libs/ui/app/integrations/memory.tsx @@ -0,0 +1,218 @@ +"use client" + +import * as React from "react" +import Image from "next/image" +import { useRouter } from "next/navigation" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { siteConfig } from "@/config/site" +import { Api } from "@/lib/api" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Skeleton } from "@/components/ui/skeleton" +import { Spinner } from "@/components/ui/spinner" + +const motorheadSchema = z.object({ + MEMORY_API_URL: z.string(), +}) + +const redisSchema = z.object({ + REDIS_MEMORY_URL: z.string(), + REDIS_MEMORY_WINDOW: z.string(), +}) + +const formSchema = z.object({ + options: z.union([motorheadSchema, redisSchema]), +}) + +export default function Memory({ + profile, + configuredMemories, +}: { + profile: any + configuredMemories: any +}) { + const [open, setOpen] = React.useState() + const [selectedDB, setSelectedDB] = React.useState() + const router = useRouter() + const api = new Api(profile.api_key) + const { ...form } = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + options: {}, + }, + }) + + async function onSubmit(values: z.infer) { + const payload = { + ...values, + options: + Object.keys(values.options).length === 0 ? undefined : values.options, + } + + const isExistingConnection = configuredMemories.find( + (db: any) => db.provider === selectedDB.provider + ) + + if (isExistingConnection) { + await api.patchMemoryDb(isExistingConnection.id, { + ...payload, + provider: selectedDB.provider, + }) + } else { + await api.createMemoryDb({ ...payload, provider: selectedDB.provider }) + } + + form.reset() + router.refresh() + setOpen(false) + } + + return ( +
+
+

Storage

+

+ Connect your vector database to store your embeddings in your own + databases. +

+
+
+ {siteConfig.memoryDbs.map((memoryDb) => { + const isConfigured = configuredMemories.find( + (db: any) => db.provider === memoryDb.provider + ) + + return ( +
+
+ {isConfigured ? ( +
+ ) : ( +
+ )} +
+ {memoryDb.name} +

{memoryDb.name}

+
+
+ +
+ ) + })} +
+ { + if (!isOpen) { + form.reset() + } + + setOpen(isOpen) + }} + open={open} + > + + + {selectedDB?.name} + + Connect your private {selectedDB?.name} account to Superagent. + + +
+
+ + {selectedDB?.metadata.map((metadataField: any) => ( + ( + + {metadataField.label} + {metadataField.type === "input" && ( + + {/* @ts-ignore */} + + + )} + {"helpText" in metadataField && ( + + {metadataField.helpText as string} + + )} + + + )} + /> + ))} + + + + + + + + +
+
+
+
+ ) +} diff --git a/libs/ui/app/integrations/page.tsx b/libs/ui/app/integrations/page.tsx index 52f462813..3e235f804 100644 --- a/libs/ui/app/integrations/page.tsx +++ b/libs/ui/app/integrations/page.tsx @@ -17,9 +17,15 @@ export default async function Integration() { .single() const api = new Api(profile.api_key) - const [{ data: configuredDBs }, { data: configuredLLMs }] = await Promise.all( - [await api.getVectorDbs(), await api.getLLMs()] - ) + const [ + { data: configuredDBs }, + { data: configuredLLMs }, + { data: configuredMemories }, + ] = await Promise.all([ + await api.getVectorDbs(), + await api.getLLMs(), + await api.getMemoryDbs(), + ]) return (
@@ -29,6 +35,7 @@ export default async function Integration() { profile={profile} configuredDBs={configuredDBs} configuredLLMs={configuredLLMs} + configuredMemories={configuredMemories} />
diff --git a/libs/ui/config/site.ts b/libs/ui/config/site.ts index e2626b430..dbc26c572 100644 --- a/libs/ui/config/site.ts +++ b/libs/ui/config/site.ts @@ -1,4 +1,4 @@ -import { VectorDbProvider } from "@/models/models" +import { MemoryDbProvider, VectorDbProvider } from "@/models/models" import { TbBrandDiscord, TbFileCode, @@ -593,4 +593,52 @@ export const siteConfig = { ], }, ], + memoryDbs: [ + { + provider: MemoryDbProvider[MemoryDbProvider.MOTORHEAD], + name: "Motorhead", + logo: "/motorhead.png", + description: + "Cloud-based database for storing and searching vectors, enabling fast similarity comparisons. Scales well for large datasets.", + formDescription: "Please enter your Motorhead api URL.", + metadata: [ + { + key: "MEMORY_API_URL", + type: "input", + label: "Memory api URL", + }, + ], + }, + { + provider: MemoryDbProvider[MemoryDbProvider.REDIS], + name: "Redis", + logo: "/redis.png", + description: + "Open-source database optimized for efficient vector search and filtering. Handles large datasets effectively while requiring minimal resources.", + formDescription: "Please enter your Redis options.", + metadata: [ + { + key: "REDIS_MEMORY_URL", + type: "input", + label: "Redis memory URL", + }, + { + key: "REDIS_MEMORY_WINDOW", + type: "input", + label: "Redis memory window size", + }, + ], + }, + ], + defaultMemory: "MOTORHEAD", + memories: [ + { + value: "MOTORHEAD", + title: "motorhead", + }, + { + value: "REDIS", + title: "redis", + }, + ], } diff --git a/libs/ui/lib/api.ts b/libs/ui/lib/api.ts index 68f1ab77e..b73433044 100644 --- a/libs/ui/lib/api.ts +++ b/libs/ui/lib/api.ts @@ -281,4 +281,22 @@ export class Api { body: JSON.stringify(payload), }) } + + async getMemoryDbs() { + return this.fetchFromApi(`/memory-dbs`) + } + + async createMemoryDb(payload: any) { + return this.fetchFromApi("/memory-db", { + method: "POST", + body: JSON.stringify(payload), + }) + } + + async patchMemoryDb(id: string, payload: any) { + return this.fetchFromApi(`/memory-dbs/${id}`, { + method: "PATCH", + body: JSON.stringify(payload), + }) + } } diff --git a/libs/ui/models/models.ts b/libs/ui/models/models.ts index a6c5f5287..cdb652cc1 100644 --- a/libs/ui/models/models.ts +++ b/libs/ui/models/models.ts @@ -68,6 +68,11 @@ export enum VectorDbProvider { SUPABASE, } +export enum MemoryDbProvider { + MOTORHEAD, + REDIS, +} + export class ApiUser { id: string token?: string diff --git a/libs/ui/public/75705874.png:Zone.Identifier b/libs/ui/public/75705874.png:Zone.Identifier new file mode 100644 index 000000000..ec9aaa7f5 --- /dev/null +++ b/libs/ui/public/75705874.png:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://github.com/getmetal/motorhead +HostUrl=https://avatars.githubusercontent.com/u/75705874?s=48&v=4 diff --git a/libs/ui/public/motorhead.png b/libs/ui/public/motorhead.png new file mode 100644 index 0000000000000000000000000000000000000000..20af48a4f6cf485b7654c522c3969a991b160b1d GIT binary patch literal 1612 zcmV-S2DABzP)Y}p8ONXVHe<&#acn24lejk_?d7IY?!CFR5~XaA3R_g6BC$elsqq7p4F3YAKW3M6R~8aJ)&cs%yJobw!XW;{-Sg{QrannjO}tVuHGoL`>* z^X9|XFJFhVH0av~_}}pkXxun+K>Sp9F9`Y7kM`%>@a|Ua{!T5=5;niMdTKoS*3HeE z*N#5-0et!W-TjvM=Sz3&np2c)t=_$|>N~aBLV3oiFMR#-Zk|=A2y}7I_<&V1kX8I7Bcpy!dkA52byz3%9F%H2xyH;9t|cG_L*b|U_6uw(WvzQ z|20DNMx%Ys4IVu&@3u=zGXpBb&_Y^i#D2sk12mLOVX8kXlycKmF02#;g-E?idpou-H(>q5BDn*0ZK+H znPDQ7aSXcBYoU@e=RY%)Cb5!vNE}W^VbJX!by@|dR4fX|vA=nB>$|@g50tw>ON^gR z#+j5_2Oc3ba}+())MP9(IcH9#JUv^LQPg?ap@5f{JB}Ss4k&e?1jw>&;8qZ#3WK22 zN(qkVhzylU5M^h|o$C3K#r5iJZ|pg*Bbjj^1)2cR@_c>J^C5~t&l|K~nV2WWml)^5 zwuHrp@+HZ_5|m9(`Q5(h^YG6HaIDf=Ene;*1L>EER`ZDiurL^Rqa>R7#E73&(%) z$OY8&3R0fNr-+lL5N_tQtu;VWISM$V4%wIzky19cRQ^ zEDBRlvT%!He6Ul=Q(tpA_1&%6w$Doi$%slDS@Vye2BJ-^O^RsAEhS+kM~37KKqcY= zgc1DqZ}mK9<#TGue!00c2;qckiG_@j;^8NzKPEHFSSd{`;a`WvKR!J-F(3f=$-R|l zEjxG~4e!brIiBymoW5gu?3vq;H@(_3f z`0<~XH}|SJ4|j3m``*Z_?Z;E8DxC3pe+YIEWCA3i(k-6jbH z06Q(`!A@m;u{|XuC7O<~^a&=@(?bwJt4DAD{^AesT*yyjT9-GtI4^K@V`J~e#$KZm z2xeO6q+K*r3PCAo_t|eAE&TM)%ZI(f>u&xgK-tij>fMhUo%gE#OwsfeM}gRDPdzzs zAMRIT^@cZ3pXL^pJU%EtKFD=7lYgAKd+K*UeNy7$|{2Z0000< KMNUMnLSTa1O7gV; literal 0 HcmV?d00001 diff --git a/libs/ui/public/redis.png b/libs/ui/public/redis.png new file mode 100644 index 0000000000000000000000000000000000000000..82437391a91c1694b3d275f0dec4c8480977ea4c GIT binary patch literal 8975 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 llmModel: string + memory: string tools: Array<{ [key: string]: any }> datasources: Array<{ [key: string]: any }> prompt: string