From 0d5228d68158a21877af739b25f5bc2b4c3a2399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Est=C3=A9vez?= Date: Mon, 22 Jul 2024 13:26:30 -0400 Subject: [PATCH] Astra Assistants Support (#2041) * ruff * revert config.yaml * add build_config * ruff * add build_config --- poetry.lock | 22 ++++++- pyproject.toml | 1 + .../components/astra_assistants/__init__.py | 13 ++++ .../astra_assistants/create_assistant.py | 48 ++++++++++++++ .../astra_assistants/create_thread.py | 25 ++++++++ .../components/astra_assistants/dotenv.py | 28 +++++++++ .../astra_assistants/get_assistant.py | 28 +++++++++ .../components/astra_assistants/getenvvar.py | 14 +++++ .../astra_assistants/list_assistants.py | 18 ++++++ .../components/astra_assistants/run.py | 63 +++++++++++++++++++ src/frontend/harFiles/langflow.har | 2 +- src/frontend/package-lock.json | 1 + src/frontend/src/style/applies.css | 20 +++--- src/frontend/tailwind.config.mjs | 6 +- 14 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 src/backend/base/langflow/components/astra_assistants/__init__.py create mode 100644 src/backend/base/langflow/components/astra_assistants/create_assistant.py create mode 100644 src/backend/base/langflow/components/astra_assistants/create_thread.py create mode 100644 src/backend/base/langflow/components/astra_assistants/dotenv.py create mode 100644 src/backend/base/langflow/components/astra_assistants/get_assistant.py create mode 100644 src/backend/base/langflow/components/astra_assistants/getenvvar.py create mode 100644 src/backend/base/langflow/components/astra_assistants/list_assistants.py create mode 100644 src/backend/base/langflow/components/astra_assistants/run.py diff --git a/poetry.lock b/poetry.lock index e8dd1b61a..cf5e4a5ee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -282,6 +282,25 @@ websockets = ">=11.0" [package.extras] extras = ["pyaudio (>=0.2.13)"] +[[package]] +name = "astra-assistants" +version = "2.0.15" +description = "Astra Assistants API - drop in replacement for OpenAI Assistants, powered by AstraDB" +optional = false +python-versions = "<4.0,>=3.10" +files = [ + {file = "astra_assistants-2.0.15-py3-none-any.whl", hash = "sha256:aa7d9479cf1a501f6edfc666a6d64028866ca8111d2936c4ae5b0649958252cb"}, + {file = "astra_assistants-2.0.15.tar.gz", hash = "sha256:6daa30f5db3f8460bba8272b3c7bb096c25310cf9f866c8b7b90505e9037138e"}, +] + +[package.dependencies] +aiohttp = ">=3.9.4,<4.0.0" +boto3 = ">=1.34.31,<2.0.0" +httpx = ">=0.27.0,<0.28.0" +litellm = ">=1.36.0,<2.0.0" +openai = ">=1.20.0,<2.0.0" +python-dotenv = ">=1.0.1,<2.0.0" + [[package]] name = "astrapy" version = "1.4.0" @@ -6336,6 +6355,7 @@ description = "Nvidia JIT LTO Library" optional = true python-versions = ">=3" files = [ + {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_aarch64.whl", hash = "sha256:98103729cc5226e13ca319a10bbf9433bbbd44ef64fe72f45f067cacc14b8d27"}, {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f9b37bc5c8cf7509665cb6ada5aaa0ce65618f2332b7d3e78e9790511f111212"}, {file = "nvidia_nvjitlink_cu12-12.5.82-py3-none-win_amd64.whl", hash = "sha256:e782564d705ff0bf61ac3e1bf730166da66dd2fe9012f111ede5fc49b64ae697"}, ] @@ -11816,4 +11836,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "1c676440f3d6e74df8460f61ce3106255cc6b5158fcf798de368e4e7d623f19a" +content-hash = "dc3c3bf982b64e4f8cf62249471bec4a90e76bde0bd010b4e2e6f6ca0d6a551b" diff --git a/pyproject.toml b/pyproject.toml index 5905f3607..3f481ff63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,7 @@ langsmith = "^0.1.86" yfinance = "^0.2.40" langchain-google-community = "^1.0.6" wolframalpha = "^5.1.3" +astra-assistants = "^2.0.15" [tool.poetry.group.dev.dependencies] diff --git a/src/backend/base/langflow/components/astra_assistants/__init__.py b/src/backend/base/langflow/components/astra_assistants/__init__.py new file mode 100644 index 000000000..515aff38a --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/__init__.py @@ -0,0 +1,13 @@ +from .create_assistant import AssistantsCreateAssistant +from .get_assistant import AssistantsGetAssistantName +from .list_assistants import AssistantsListAssistants +from .run import AssistantsRun +from .getenvvar import GetEnvVar + +__all__ = [ + "AssistantsCreateAssistant", + "AssistantsGetAssistantName", + "AssistantsListAssistants", + "AssistantsRun", + "GetEnvVar", +] diff --git a/src/backend/base/langflow/components/astra_assistants/create_assistant.py b/src/backend/base/langflow/components/astra_assistants/create_assistant.py new file mode 100644 index 000000000..50df68e78 --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/create_assistant.py @@ -0,0 +1,48 @@ +from langflow.custom import CustomComponent +from openai import OpenAI +from astra_assistants import patch + + +class AssistantsCreateAssistant(CustomComponent): + display_name = "Create Assistant" + description = "Creates an Assistant and returns it's id" + + def build_config(self): + return { + "name": { + "display_name": "Assistant Name", + "advanced": False, + "info": "Name for the assistant being created", + }, + "instructions": { + "display_name": "Instructions", + "info": "Instructions for the assistant, think of these as the system prompt.", + "advanced": False, + }, + "model": { + "display_name": "Model name", + "advanced": False, + "info": ( + "Model for the assistant.\n\n" + "Environment variables for provider credentials can be set with the Dotenv Component.\n\n" + "Models are supported via LiteLLM, see (https://docs.litellm.ai/docs/providers) for supported model names and env vars." + ), + }, + "env_set": { + "display_name": "Environment Set", + "advanced": False, + "info": "Dummy input to allow chaining with Dotenv Component.", + }, + } + + def build(self, name: str, instructions: str, model: str, env_set: str = None) -> str: + print(f"env_set is {env_set}") + if env_set is None: + raise Exception("Environment variables not set") + client = patch(OpenAI()) + assistant = client.beta.assistants.create( + name=name, + instructions=instructions, + model=model, + ) + return assistant.id diff --git a/src/backend/base/langflow/components/astra_assistants/create_thread.py b/src/backend/base/langflow/components/astra_assistants/create_thread.py new file mode 100644 index 000000000..5c6b26aa2 --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/create_thread.py @@ -0,0 +1,25 @@ +from langflow.custom import CustomComponent +from openai import OpenAI +from astra_assistants import patch + + +class AssistantsCreateThread(CustomComponent): + display_name = "Create Assistant Thread" + description = "Creates a thread and returns the thread id" + + def build_config(self): + return { + "env_set": { + "display_name": "Environment Set", + "advanced": False, + "info": "Dummy input to allow chaining with Dotenv Component.", + }, + } + + def build(self, env_set: str = None) -> str: + client = patch(OpenAI()) + + thread = client.beta.threads.create() + thread_id = thread.id + + return thread_id diff --git a/src/backend/base/langflow/components/astra_assistants/dotenv.py b/src/backend/base/langflow/components/astra_assistants/dotenv.py new file mode 100644 index 000000000..d4abf8147 --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/dotenv.py @@ -0,0 +1,28 @@ +import io +from dotenv import load_dotenv +from langflow.custom import CustomComponent + + +class Dotenv(CustomComponent): + display_name = "Dotenv" + description = "Load .env file into env vars" + + def build_config(self): + return { + "dotenv_file_content": { + "display_name": "Dotenv file content", + "advanced": False, + "info": ( + "Paste the content of your .env file directly\n\n" + "Since contents are sensitive, using a Global variable set as 'password' is recommended" + ), + }, + } + + def build(self, dotenv_file_content: str) -> str: + try: + fake_file = io.StringIO(dotenv_file_content) + result = load_dotenv(stream=fake_file, override=True) + return result + except Exception as e: + raise e diff --git a/src/backend/base/langflow/components/astra_assistants/get_assistant.py b/src/backend/base/langflow/components/astra_assistants/get_assistant.py new file mode 100644 index 000000000..ab8b7f0da --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/get_assistant.py @@ -0,0 +1,28 @@ +from langflow.custom import CustomComponent +from openai import OpenAI +from astra_assistants import patch + + +class AssistantsGetAssistantName(CustomComponent): + display_name = "Get Assistant name" + description = "Assistant by id" + + def build_config(self): + return { + "assistant_id": { + "display_name": "Assistant ID", + "advanced": False, + }, + "env_set": { + "display_name": "Environment Set", + "advanced": False, + "info": "Dummy input to allow chaining with Dotenv Component.", + }, + } + + def build(self, assistant_id: str, env_set: str = None) -> str: + client = patch(OpenAI()) + assistant = client.beta.assistants.retrieve( + assistant_id=assistant_id, + ) + return assistant.name diff --git a/src/backend/base/langflow/components/astra_assistants/getenvvar.py b/src/backend/base/langflow/components/astra_assistants/getenvvar.py new file mode 100644 index 000000000..164117c15 --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/getenvvar.py @@ -0,0 +1,14 @@ +import os +from langflow.custom import CustomComponent + + +class GetEnvVar(CustomComponent): + display_name = "Get env var" + description = "Get env var" + icon = "custom_components" + + def build_config(self): + return {"env_var_name": {"display_name": "Env var name"}} + + def build(self, env_var_name: str) -> str: + return os.environ[env_var_name] diff --git a/src/backend/base/langflow/components/astra_assistants/list_assistants.py b/src/backend/base/langflow/components/astra_assistants/list_assistants.py new file mode 100644 index 000000000..e6608a63c --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/list_assistants.py @@ -0,0 +1,18 @@ +from typing import List +from langflow.custom import CustomComponent +from openai import OpenAI +from astra_assistants import patch + + +class AssistantsListAssistants(CustomComponent): + display_name = "List Assistants" + description = "Returns a list of assistant id's" + + def build_config(self): + return {} + + def build(self) -> List[str]: + client = patch(OpenAI()) + assistants = client.beta.assistants.list() + id_list = [assistant.id for assistant in assistants] + return id_list diff --git a/src/backend/base/langflow/components/astra_assistants/run.py b/src/backend/base/langflow/components/astra_assistants/run.py new file mode 100644 index 000000000..5afaaf716 --- /dev/null +++ b/src/backend/base/langflow/components/astra_assistants/run.py @@ -0,0 +1,63 @@ +from langflow.custom import CustomComponent +from openai import OpenAI +from openai.lib.streaming import AssistantEventHandler +from astra_assistants import patch + + +class AssistantsRun(CustomComponent): + display_name = "Run Assistant" + description = "Executes an Assistant Run against a thread" + + def build_config(self): + return { + "assistant_id": { + "display_name": "Assistant ID", + "advanced": False, + "info": ( + "The ID of the assistant to run. \n\n" + "Can be retrieved using the List Assistants component or created with the Create Assistant component." + ), + }, + "user_message": { + "display_name": "User Message", + "info": "User message to pass to the run.", + "advanced": False, + }, + "thread_id": { + "display_name": "Thread ID", + "advanced": False, + "info": "Thread ID to use with the run. If not provided, a new thread will be created.", + }, + "env_set": { + "display_name": "Environment Set", + "advanced": False, + "info": "Dummy input to allow chaining with Dotenv Component.", + }, + } + + def build(self, assistant_id: str, user_message: str, thread_id: str = None, env_set: str = None) -> str: + text = "" + client = patch(OpenAI()) + + if thread_id is None: + thread = client.beta.threads.create() + thread_id = thread.id + + # add the user message + client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_message) + + class EventHandler(AssistantEventHandler): + def __init__(self): + super().__init__() + + event_handler = EventHandler() + with client.beta.threads.runs.create_and_stream( + thread_id=thread_id, + assistant_id=assistant_id, + event_handler=event_handler, + ) as stream: + # return stream.text_deltas + for part in stream.text_deltas: + text += part + print(part) + return text diff --git a/src/frontend/harFiles/langflow.har b/src/frontend/harFiles/langflow.har index dcd0b23aa..a2b319dae 100644 --- a/src/frontend/harFiles/langflow.har +++ b/src/frontend/harFiles/langflow.har @@ -1619,4 +1619,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index ed2b933c3..cc351acc6 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -789,6 +789,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/src/frontend/src/style/applies.css b/src/frontend/src/style/applies.css index cdf769915..8c0f2cb5c 100644 --- a/src/frontend/src/style/applies.css +++ b/src/frontend/src/style/applies.css @@ -80,7 +80,7 @@ @apply w-full truncate pr-1 text-xs text-foreground; } .side-bar-components-div-form { - @apply flex w-full items-center justify-between rounded-md rounded-l-none border border-l-0 border-dashed border-ring bg-white px-3 py-1 text-sm; + @apply flex w-full items-center justify-between rounded-md rounded-l-none border border-l-0 border-dashed border-ring bg-white px-3 py-1 text-sm; } .side-bar-components-border { @apply cursor-grab rounded-l-md border-l-8; @@ -118,7 +118,7 @@ /* Frozen state border */ .border-ring-frozen { position: relative; - @apply rounded-md border shadow-frozen-ring; + @apply rounded-md border shadow-frozen-ring; } .border-ring-frozen::before { content: ""; @@ -440,10 +440,10 @@ } .unused-side-bar-aside { - @apply flex flex-shrink-0 flex-col overflow-hidden border-r transition-all duration-500; + @apply flex flex-shrink-0 flex-col overflow-hidden border-r transition-all duration-500; } .unused-side-bar-arrangement { - @apply flex h-full w-52 flex-col items-start overflow-y-auto border bg-background scrollbar-hide; + @apply flex h-full w-52 flex-col items-start overflow-y-auto border bg-background scrollbar-hide; } .unused-side-bar-division { @apply flex-max-width flex-grow flex-col; @@ -593,13 +593,13 @@ @apply inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 shadow-sm; } .header-waitlist-link-box { - @apply inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md border border-input px-2 text-sm font-medium text-muted-foreground shadow-sm ring-offset-background disabled:pointer-events-none disabled:opacity-50; + @apply inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md border border-input px-2 text-sm font-medium text-muted-foreground shadow-sm ring-offset-background disabled:pointer-events-none disabled:opacity-50; } .header-waitlist-link-box:hover { @apply hover:bg-accent hover:text-accent-foreground; } .header-github-link { - @apply header-github-link-box text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50; + @apply header-github-link-box text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50; } .header-github-link:focus-visible { @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2; @@ -647,7 +647,7 @@ @apply focus:outline-none focus:ring-1 focus:ring-primary focus:ring-offset-1; } .toggle-component-span { - @apply pointer-events-none relative h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out; + @apply pointer-events-none relative h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out; } .toggle-component-second-span { @apply absolute inset-0 flex h-full w-full items-center justify-center transition-opacity; @@ -658,7 +658,7 @@ } .chat-input-modal-txtarea { - @apply form-input block w-full rounded-md border-ring pr-10 custom-scroll sm:text-sm; + @apply form-input block w-full rounded-md border-ring pr-10 custom-scroll sm:text-sm; } .chat-input-modal-div { @apply absolute bottom-0.5 right-3; @@ -693,7 +693,7 @@ @apply flex-max-width items-center text-start; } .chat-message-modal-text { - @apply relative w-full text-start text-sm font-normal text-muted-foreground; + @apply relative w-full text-start text-sm font-normal text-muted-foreground; } .chat-message-modal-icon-div { @apply absolute -left-2 -top-1 cursor-pointer; @@ -1128,7 +1128,7 @@ } .input-invalid { - @apply border-destructive focus:border-destructive focus:ring-destructive; + @apply border-destructive focus:border-destructive focus:ring-destructive; } .fade-container { diff --git a/src/frontend/tailwind.config.mjs b/src/frontend/tailwind.config.mjs index 2fd7cb24a..3d98fd624 100644 --- a/src/frontend/tailwind.config.mjs +++ b/src/frontend/tailwind.config.mjs @@ -1,9 +1,9 @@ /** @type {import('tailwindcss').Config} */ -import { fontFamily } from "tailwindcss/defaultTheme"; import tailwindcssForms from "@tailwindcss/forms"; -import tailwindcssAnimate from "tailwindcss-animate"; import tailwindcssTypography from "@tailwindcss/typography"; +import tailwindcssAnimate from "tailwindcss-animate"; import tailwindcssDottedBackground from "tailwindcss-dotted-background"; +import { fontFamily } from "tailwindcss/defaultTheme"; import plugin from "tailwindcss/plugin"; @@ -254,4 +254,4 @@ const config = { ], }; -export default config; \ No newline at end of file +export default config;