feat: Add Hybrid Search functionality to AstraDB + AstraPy / LangChain Updates (#7358)

* feat: Add Hybrid Search functionality and AstraPy 2.0 and associated deps (#7357)

* astrapy 2.0 tentative full pass

* Update the create collection function

---------

Co-authored-by: Stefano Lottini <stefano.lottini@datastax.com>

* Update deps

* Update uv.lock

* Fix linting errors in astradb

* Update package lock

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Add basic UI scaffolding for hybrid search

* [autofix.ci] apply automated fixes

* Continue to clean up component

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Fix the keyspace compatibility

* [autofix.ci] apply automated fixes

* feat: add nodeId, nodeClass, and handleNodeClass props to dropdown an… (#7406)

feat: add nodeId, nodeClass, and handleNodeClass props to dropdown and string render components

Co-authored-by: deon-sanchez <deon.sanchez@datastax.com>

* Update uv.lock

* Update uv.lock

* Add hybrid search support in collection creation

* [autofix.ci] apply automated fixes

* Updates from review comments

* [autofix.ci] apply automated fixes

* Add in lexical search support

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Detect collection hybrid params

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Pass lexical terms at search time

* [autofix.ci] apply automated fixes

* Update test_astra_component.py

* Add Query Input and Mixin on backend

* Adds Query on supported types

* Adds types for query modal and component

* Adds size for new query modal

* Adds query modal

* Adds query component

* Adds query component on parameter render

* [autofix.ci] apply automated fixes

* Feedback from review

* [autofix.ci] apply automated fixes

* ✨ (switch-case-size.ts): Update height value to 'h-fit' for 'small-query' case to improve responsiveness
✨ (queryInputComponent.spec.ts): Add unit test for user interaction with query input component, including updating code and testing functionality

* Switch to multiline for lexical terms

* [autofix.ci] apply automated fixes

* Create Hybrid Search RAG.json

* Update Hybrid Search RAG.json

* Added queryInput in vectorstore model

* Added queryInput in lexical terms

* Update model.py

* Update Hybrid Search RAG.json

* Add query support in field validation

* fix: bump Astra Assistants version to support AstraPy 2.0 (#7535)

2.2.12

Co-authored-by: phact <estevezsebastian@gmail.com>

* Update uv.lock

* Fixed QueryInput not receiving text from handle

* Set search type to similarity search when hybrid

* Always set to similarity when we have the reranker

* [autofix.ci] apply automated fixes

* Add logging for hybrid search support

* Update starter projects

* Update Hybrid Search RAG.json

* Added dropdown toggle on backend

* Added toggle on dropdown on frontend

* Added showing only value if there is just one option in the dropdown

* Added toggle to Dropdown Input on Astra Db

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* init toggle value as true or false

* Change it to send null value if toggle is disabled

* Added resizer on search query

* Added Search Hybrid, Lexical and Vector icons

* Added icons and new Lexical Search on Dropdown Input of Astra DB

* Updated starter projects

* Changed descriptions on astradb component

* Changed starter projects

* Lexical search option for dropdown

* Update astradb.py

* Update starter projects

* One small lexical update

* Update astradb.py

* Update projects

* [autofix.ci] apply automated fixes

* Fixed dropdown changing when toggle is off

* Update astradb.py

* [autofix.ci] apply automated fixes

* Don't show lexical terms on new collection creation

* ✨ (actionsMainPage-shard-0.spec.ts): add functionality to add flow to test on empty langflow button click
✨ (filterEdge-shard-1.spec.ts): add functionality to add flow to test on empty langflow button click
♻️ (await-bootstrap-test.ts): refactor code to reuse addFlowToTestOnEmptyLangflow function for adding flow to test on empty langflow button click

* [autofix.ci] apply automated fixes

* 🐛 (filterEdge-shard-1.spec.ts): fix incorrect reference to memoriesAstra DB Chat Memory, update to memoriesMem0 Chat Memory for accurate testing data.

---------

Co-authored-by: Stefano Lottini <stefano.lottini@datastax.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: deon-sanchez <deon.sanchez@datastax.com>
Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: phact <estevezsebastian@gmail.com>
This commit is contained in:
Eric Hare 2025-04-11 11:03:34 -07:00 • committed by GitHub
commit 907a594428
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 4901 additions and 1804 deletions

View file

@ -53,6 +53,7 @@ export default function Dropdown({
name,
dialogInputs,
handleOnNewValue,
toggle,
...baseInputProps
}: BaseInputProps & DropDownComponent): JSX.Element {
const validOptions = useMemo(
@ -482,6 +483,19 @@ export default function Dropdown({
<PopoverAnchor>{children}</PopoverAnchor>
) : refreshOptions || isLoading ? (
renderLoadingButton()
) : validOptions.length === 1 &&
toggle &&
!combobox &&
value === validOptions[0] ? (
<div className="flex w-full items-center gap-2 truncate">
{optionsMetaData?.[0]?.icon && (
<ForwardedIconComponent
name={optionsMetaData?.[0]?.icon}
className="h-4 w-4 flex-shrink-0"
/>
)}
<span className="truncate text-sm">{value}</span>
</div>
) : (
<div className="w-full truncate">{renderTriggerButton()}</div>
)}

View file

@ -1,5 +1,6 @@
import Dropdown from "../../../dropdownComponent";
import { DropDownComponentType, InputProps } from "../../types";
import ToggleShadComponent from "../toggleShadComponent";
export default function DropdownComponent({
id,
@ -15,6 +16,8 @@ export default function DropdownComponent({
nodeClass,
nodeId,
handleNodeClass,
toggle,
toggleValue,
...baseInputProps
}: InputProps<string, DropDownComponentType>) {
const onChange = (value: any, dbValue?: boolean, skipSnapshot?: boolean) => {
@ -22,22 +25,39 @@ export default function DropdownComponent({
};
return (
<Dropdown
disabled={disabled}
editNode={editNode}
options={options}
nodeId={nodeId}
nodeClass={nodeClass}
handleNodeClass={handleNodeClass}
optionsMetaData={optionsMetaData}
onSelect={onChange}
combobox={combobox}
value={value || ""}
id={`dropdown_${id}`}
name={name}
dialogInputs={dialogInputs}
handleOnNewValue={handleOnNewValue}
{...baseInputProps}
/>
<div className="flex w-full items-center gap-4">
<Dropdown
disabled={disabled || toggleValue === false}
editNode={editNode}
toggle={toggle}
options={options}
nodeId={nodeId}
nodeClass={nodeClass}
handleNodeClass={handleNodeClass}
optionsMetaData={optionsMetaData}
onSelect={onChange}
combobox={combobox}
value={value || (toggleValue === false && toggle ? options[0] : "")}
id={`dropdown_${id}`}
name={name}
dialogInputs={dialogInputs}
handleOnNewValue={handleOnNewValue}
{...baseInputProps}
/>
{toggle && (
<ToggleShadComponent
value={toggleValue ?? true}
handleOnNewValue={(data) => {
handleOnNewValue({
value: data.value === true ? options[0] : null,
toggle_value: data.value,
});
}}
editNode={editNode}
id={`toggle_dropdown_${id}`}
disabled={disabled}
/>
)}
</div>
);
}

View file

@ -76,6 +76,8 @@ export function StrRenderComponent({
optionsMetaData={templateData.options_metadata}
combobox={templateData.combobox}
name={templateData?.name!}
toggle={templateData.toggle}
toggleValue={templateData.toggle_value}
/>
);
}

View file

@ -97,6 +97,8 @@ export type DropDownComponentType = {
nodeId: string;
nodeClass: APIClassType;
handleNodeClass: (value: any, code?: string, type?: string) => void;
toggle?: boolean;
toggleValue?: boolean;
};
export type TextAreaComponentType = {

View file

@ -0,0 +1,23 @@
const SvgSearchHybridIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path d="M15.75 15.75L12.525 12.525L15.75 15.75Z" fill="currentColor" />
<path
d="M1.5 11.625C3.36396 11.625 4.875 10.114 4.875 8.25C4.875 10.114 6.38604 11.625 8.25 11.625C6.38604 11.625 4.875 13.136 4.875 15C4.875 13.136 3.36396 11.625 1.5 11.625Z"
fill="currentColor"
/>
<path
d="M15.75 15.75L12.525 12.525M2.43903 6.75C3.10509 4.16216 5.45424 2.25 8.25 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25C14.25 11.0458 12.3378 13.3949 9.75 14.061M4.875 8.25C4.875 10.114 3.36396 11.625 1.5 11.625C3.36396 11.625 4.875 13.136 4.875 15C4.875 13.136 6.38604 11.625 8.25 11.625C6.38604 11.625 4.875 10.114 4.875 8.25Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
export default SvgSearchHybridIcon;

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import SvgSearchHybridIcon from "./SearchHybridIcon";
export const SearchHybridIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgSearchHybridIcon ref={ref} {...props} />;
});

View file

@ -0,0 +1,22 @@
const SvgSearchLexicalIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path d="M15.75 15.75L12.525 12.525L15.75 15.75Z" fill="currentColor" />
<path d="M3.75 15H5.25H6.75" fill="currentColor" />
<path d="M5.25 9.75V15V9.75Z" fill="currentColor" />
<path d="M8.25 11.25V9.75H5.25H2.25V11.25" fill="currentColor" />
<path
d="M15.75 15.75L12.525 12.525M2.43903 6.75C3.1051 4.16216 5.45425 2.25 8.25001 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25C14.25 11.0458 12.3378 13.3949 9.75001 14.061M3.75 15H5.25M6.75 15H5.25M5.25 9.75V15M5.25 9.75H8.25V11.25M5.25 9.75H2.25V11.25"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
export default SvgSearchLexicalIcon;

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import SvgSearchLexicalIcon from "./SearchLexicalIcon";
export const SearchLexicalIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgSearchLexicalIcon ref={ref} {...props} />;
});

View file

@ -0,0 +1,19 @@
const SvgSearchVectorIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
>
<path d="M15.75 15.75L12.525 12.525L15.75 15.75Z" fill="currentColor" />
<path
d="M15.75 15.75L12.525 12.525M2.43903 6.75C3.1051 4.16216 5.45425 2.25 8.25001 2.25C11.5637 2.25 14.25 4.93629 14.25 8.25C14.25 11.0458 12.3378 13.3949 9.75001 14.061M2.25 14.25V11.25M2.25 14.25H5.25M2.25 14.25L7.5 9"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
export default SvgSearchVectorIcon;

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import SvgSearchVectorIcon from "./SearchVectorIcon";
export const SearchVectorIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgSearchVectorIcon ref={ref} {...props} />;
});

View file

@ -47,7 +47,7 @@ export default function QueryModal({
<div className={classNames("flex h-full w-full rounded-lg border")}>
<Textarea
ref={textRef}
className="form-input h-full w-full resize-none overflow-auto rounded-lg focus-visible:ring-1"
className="form-input h-full min-h-28 w-full overflow-auto rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);

View file

@ -67,6 +67,7 @@ export type DropDownComponent = {
children?: ReactNode;
name: string;
dialogInputs?: any;
toggle?: boolean;
};
export type ParameterComponentType = {
selected?: boolean;

View file

@ -14,6 +14,9 @@ import { MilvusIcon } from "@/icons/Milvus";
import { OneDriveIcon } from "@/icons/OneDrive";
import Perplexity from "@/icons/Perplexity/Perplexity";
import { SearchAPIIcon } from "@/icons/SearchAPI";
import { SearchHybridIcon } from "@/icons/SearchHybrid";
import { SearchLexicalIcon } from "@/icons/SearchLexical";
import { SearchVectorIcon } from "@/icons/SearchVector";
import { SerpSearchIcon } from "@/icons/SerpSearch";
import { TavilyIcon } from "@/icons/Tavily";
import { UnstructuredIcon } from "@/icons/Unstructured";
@ -754,6 +757,9 @@ export const nodeIconsLucide: iconsType = {
OpenAI: OpenAiIcon,
OpenRouter: OpenRouterIcon,
DeepSeek: DeepSeekIcon,
SearchLexical: SearchLexicalIcon,
SearchHybrid: SearchHybridIcon,
SearchVector: SearchVectorIcon,
xAI: XAIIcon,
OpenAIEmbeddings: OpenAiIcon,
Pinecone: PineconeIcon,

View file

@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test";
import { addFlowToTestOnEmptyLangflow } from "../../utils/add-flow-to-test-on-empty-langflow";
import { adjustScreenView } from "../../utils/adjust-screen-view";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
@ -62,6 +63,12 @@ test(
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
const countEmptyButton = await page
.getByTestId("new_project_btn_empty_page")
.count();
if (countEmptyButton > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.getByTestId("upload-folder-button").last().click();
},
);

View file

@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test";
import { addFlowToTestOnEmptyLangflow } from "../../utils/add-flow-to-test-on-empty-langflow";
import { addLegacyComponents } from "../../utils/add-legacy-components";
import { adjustScreenView } from "../../utils/adjust-screen-view";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
@ -9,6 +10,10 @@ test(
async ({ page }) => {
await awaitBootstrapTest(page);
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="sidebar-options-trigger"]', {
@ -73,7 +78,7 @@ test(
"vectorstoresAstra DB",
"langchain_utilitiesTool Calling Agent",
"langchain_utilitiesConversationChain",
"memoriesAstra DB Chat Memory",
"memoriesMem0 Chat Memory",
"logicCondition",
"langchain_utilitiesSelf Query Retriever",
"langchain_utilitiesCharacterTextSplitter",

View file

@ -1,4 +1,5 @@
import { Page } from "playwright/test";
import { addFlowToTestOnEmptyLangflow } from "./add-flow-to-test-on-empty-langflow";
export const awaitBootstrapTest = async (
page: Page,
@ -15,6 +16,13 @@ export const awaitBootstrapTest = async (
timeout: 30000,
});
const countEmptyButton = await page
.getByTestId("new_project_btn_empty_page")
.count();
if (countEmptyButton > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[id="new-project-btn"]', {
timeout: 30000,
});