Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support set Memory from UI #852

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
support set memory from ui
bdqfork committed Feb 24, 2024
commit 88ff32ebe93c31728166fd69737585de7555eb19
4 changes: 2 additions & 2 deletions libs/superagent/Dockerfile
Original file line number Diff line number Diff line change
@@ -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 . ./
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bdqfork Why did you move this line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separating code and environment dependencies allows for optimal utilization of the cache built by Docker. During the debugging phase, there are more changes in the code compared to changes in the environment dependencies.


# Enable prisma migrations
RUN prisma generate

7 changes: 6 additions & 1 deletion libs/superagent/app/agents/base.py
Original file line number Diff line number Diff line change
@@ -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()
37 changes: 28 additions & 9 deletions libs/superagent/app/agents/langchain.py
Original file line number Diff line number Diff line change
@@ -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,33 +197,48 @@ 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=(
f"{self.agent_id}-{self.session_id}"
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"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If one wants to define different REDIS_MEMORY_WINDOW per each agent. We can't achieve it by defining global memory and use it this way. Is it something we should care about? @homanp

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elisalimli @homanp Perhaps memory_size could be utilized as an agent argument, representing the number of recent conversations to give priority to. If this value exists, it will be utilized; otherwise, the global configuration will be used.

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}"
if self.session_id
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(
5 changes: 5 additions & 0 deletions libs/superagent/app/api/agents.py
Original file line number Diff line number Diff line change
@@ -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()

104 changes: 104 additions & 0 deletions libs/superagent/app/api/memory_dbs.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 2 additions & 1 deletion libs/superagent/app/main.py
Original file line number Diff line number Diff line change
@@ -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,
8 changes: 7 additions & 1 deletion libs/superagent/app/models/request.py
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions libs/superagent/app/models/response.py
Original file line number Diff line number Diff line change
@@ -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]]
2 changes: 2 additions & 0 deletions libs/superagent/app/routers.py
Original file line number Diff line number Diff line change
@@ -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)
14 changes: 12 additions & 2 deletions libs/superagent/app/workflows/base.py
Original file line number Diff line number Diff line change
@@ -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,28 +46,35 @@ 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()
agent_input = agent_base.get_input(
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}
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 17 additions & 0 deletions libs/superagent/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use memoryDbProvider:

Suggested change
memory MemoryDbProvider? @default(MOTORHEAD)
memoryDbProvider 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])
}
43 changes: 43 additions & 0 deletions libs/ui/app/agents/[agentId]/settings.tsx
Original file line number Diff line number Diff line change
@@ -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({
</div>
)}
</div>
<div>
<FormLabel>Memory</FormLabel>
{agent.memory.length > 0 ? (
<div className="flex justify-between space-x-2">
<FormField
control={form.control}
name="memory"
render={({ field }) => (
<FormItem className="flex-1">
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a memory" />
</SelectTrigger>
</FormControl>
<SelectContent>
{siteConfig.memories.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.title}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
) : (
<div className="flex flex-col space-y-4 rounded-lg border border-red-500 p-4">
<p className="text-sm">Heads up!</p>
<p className="text-sm text-muted-foreground">
You need to add an Memory to this agent for it work. This can
be done through the SDK or API.
</p>
</div>
)}
</div>

<Separator className="!my-8 flex items-center justify-center">
<span className="text-sm text-muted-foreground">
9 changes: 9 additions & 0 deletions libs/ui/app/integrations/client-page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Tabs defaultValue="storage" className="flex-1 space-y-0 overflow-hidden">
@@ -23,13 +26,19 @@ export default function IntegrationsClientPage({
<TabsTrigger value="llm" className="space-x-1">
<span>LANGUAGE MODELS</span>
</TabsTrigger>
<TabsTrigger value="memory" className="space-x-1">
<span>MEMORY</span>
</TabsTrigger>
</TabsList>
<TabsContent value="storage" className="px-6 py-2 text-sm">
<Storage profile={profile} configuredDBs={configuredDBs} />
</TabsContent>
<TabsContent value="llm" className="h-full text-sm">
<LLM profile={profile} configuredLLMs={configuredLLMs} />
</TabsContent>
<TabsContent value="memory" className="px-6 py-2 text-sm">
<Memory profile={profile} configuredMemories={configuredMemories} />
</TabsContent>
<TabsContent value="datasources" className="h-full text-sm"></TabsContent>
<TabsContent value="tools" className="h-full text-sm"></TabsContent>
</Tabs>
218 changes: 218 additions & 0 deletions libs/ui/app/integrations/memory.tsx
Original file line number Diff line number Diff line change
@@ -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(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use number instead of 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<boolean>()
const [selectedDB, setSelectedDB] = React.useState<any>()
const router = useRouter()
const api = new Api(profile.api_key)
const { ...form } = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
options: {},
},
})

async function onSubmit(values: z.infer<typeof formSchema>) {
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 (
<div className="container flex max-w-4xl flex-col space-y-10 pt-10">
<div className="flex flex-col">
<p className="text-lg font-medium">Storage</p>
<p className="text-muted-foreground">
Connect your vector database to store your embeddings in your own
databases.
</p>
</div>
<div className="flex-col border-b">
{siteConfig.memoryDbs.map((memoryDb) => {
const isConfigured = configuredMemories.find(
(db: any) => db.provider === memoryDb.provider
)

return (
<div
className="flex items-center justify-between border-t py-4"
key={memoryDb.provider}
>
<div className="flex items-center space-x-4">
{isConfigured ? (
<div className="h-2 w-2 rounded-full bg-green-400" />
) : (
<div className="h-2 w-2 rounded-full bg-muted" />
)}
<div className="flex items-center space-x-3">
<Image
src={memoryDb.logo}
width="40"
height="40"
alt={memoryDb.name}
/>
<p className="font-medium">{memoryDb.name}</p>
</div>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedDB(memoryDb)
setOpen(true)
}}
>
Settings
</Button>
</div>
)
})}
</div>
<Dialog
onOpenChange={(isOpen) => {
if (!isOpen) {
form.reset()
}

setOpen(isOpen)
}}
open={open}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{selectedDB?.name}</DialogTitle>
<DialogDescription>
Connect your private {selectedDB?.name} account to Superagent.
</DialogDescription>
</DialogHeader>
<div className="flex flex-col">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="w-full space-y-4"
>
{selectedDB?.metadata.map((metadataField: any) => (
<FormField
key={metadataField.key}
control={form.control}
// @ts-ignore
name={`options.${metadataField.key}`}
render={({ field }) => (
<FormItem>
<FormLabel>{metadataField.label}</FormLabel>
{metadataField.type === "input" && (
<FormControl>
{/* @ts-ignore */}
<Input
{...field}
placeholder={
"placeholder" in metadataField
? metadataField.placeholder
: ""
}
type="text"
/>
</FormControl>
)}
{"helpText" in metadataField && (
<FormDescription className="pb-2">
{metadataField.helpText as string}
</FormDescription>
)}
<FormMessage />
</FormItem>
)}
/>
))}
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="ghost">
Close
</Button>
</DialogClose>
<Button type="submit" size="sm">
{form.control._formState.isSubmitting ? (
<Spinner />
) : (
"Save configuration"
)}
</Button>
</DialogFooter>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
</div>
)
}
13 changes: 10 additions & 3 deletions libs/ui/app/integrations/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex h-screen flex-col justify-between space-y-0 overflow-hidden">
@@ -29,6 +35,7 @@ export default async function Integration() {
profile={profile}
configuredDBs={configuredDBs}
configuredLLMs={configuredLLMs}
configuredMemories={configuredMemories}
/>
</div>
</div>
50 changes: 49 additions & 1 deletion libs/ui/config/site.ts
Original file line number Diff line number Diff line change
@@ -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",
},
],
}
18 changes: 18 additions & 0 deletions libs/ui/lib/api.ts
Original file line number Diff line number Diff line change
@@ -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),
})
}
}
5 changes: 5 additions & 0 deletions libs/ui/models/models.ts
Original file line number Diff line number Diff line change
@@ -68,6 +68,11 @@ export enum VectorDbProvider {
SUPABASE,
}

export enum MemoryDbProvider {
MOTORHEAD,
REDIS,
}

export class ApiUser {
id: string
token?: string
4 changes: 4 additions & 0 deletions libs/ui/public/75705874.png:Zone.Identifier
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://github.com/getmetal/motorhead
HostUrl=https://avatars.githubusercontent.com/u/75705874?s=48&v=4
Binary file added libs/ui/public/motorhead.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added libs/ui/public/redis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions libs/ui/types/agent.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ export interface Agent {
isActive: boolean
llms: Array<{ [key: string]: any }>
llmModel: string
memory: string
tools: Array<{ [key: string]: any }>
datasources: Array<{ [key: string]: any }>
prompt: string