feat: Needle Search Tool With Template (#6648)

* feat: Needle Search Tool With Templte

* lint

* lint

* lint

* lint

* refactor: Use Langflow Agent instead of CrewAI Agent

* techdebt: adjust Needle component to use tool mode and remove tool component

* lint

* lint

* Update Invoice Summarizer.json

* Update Invoice Summarizer.json

* update to the component

* refactor: Use Needle icon svg

* make format

* component updates

* update with latest agent component

* updated a missing connection when updating the agent component

* update template

---------

Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
This commit is contained in:
Jan Heimes 2025-03-14 10:37:54 +01:00 • committed by GitHub
commit 94bc8dbc7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 2122 additions and 99 deletions

View file

@ -1,18 +0,0 @@
const SvgNeedleIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 32 32"
{...props}
>
<circle cx="16" cy="16" r="15" stroke="currentColor" strokeWidth="2" />
<path
fill="currentColor"
d="M6.06661 23.4341L19.2183 4.1263C19.5277 3.67209 20.3458 3.99976 20.3225 4.54884C20.1123 9.51475 23.0448 12.1637 28.0237 12.3643C28.5874 12.387 28.8315 13.2879 28.3252 13.5368L6.70039 24.1643C6.23948 24.3909 5.7775 23.8586 6.06661 23.4341Z"
/>
<circle cx="24.5" cy="8.5" r="1.5" fill="currentColor" />
</svg>
);
export default SvgNeedleIcon;

View file

@ -1,9 +1,9 @@
import React, { forwardRef } from "react";
import SvgNeedleIcon from "./NeedleIcon";
import NeedleSvg from "./needle-icon.svg?react";
export const NeedleIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgNeedleIcon ref={ref} {...props} />;
return <NeedleSvg ref={ref} {...props} />;
});

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

View file

@ -0,0 +1,102 @@
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { initialGPTsetup } from "../../utils/initialGPTsetup";
test(
"Invoice Summarizer",
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY ||
!process?.env?.NEEDLE_API_KEY ||
!process?.env?.NEEDLE_COLLECTION_ID,
"OPENAI_API_KEY, NEEDLE_API_KEY, and NEEDLE_COLLECTION_ID required to run this test",
);
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await awaitBootstrapTest(page);
await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Invoice Summarizer" }).click();
await initialGPTsetup(page);
// Configure Needle Search Knowledge Base
await page
.getByTestId("input_str_needle_api_key")
.fill(process.env.NEEDLE_API_KEY || "");
await page
.getByTestId("input_str_collection_id")
.fill(process.env.NEEDLE_COLLECTION_ID || "");
// Run the flow
await page.getByTestId("button_run_chat output").click();
// Wait for the flow to build successfully
await page.waitForSelector("text=built successfully", { timeout: 30000 });
await page.getByText("built successfully").last().click({
timeout: 30000,
});
// Switch to Playground
await page.getByText("Playground", { exact: true }).last().click();
// Wait for the playground to be ready
const inputPlaceholder = page
.getByPlaceholder(
"No chat input variables found. Click to run your flow.",
{ exact: true },
)
.last();
await expect(inputPlaceholder).toBeVisible({ timeout: 10000 });
// Verify initial response is displayed
await expect(page.getByText("Search Results Summary")).toBeVisible({
timeout: 15000,
});
// Verify that specific invoice-related data appears in the results
const keyTerms = ["expenses", "invoice", "vendor"];
for (const term of keyTerms) {
await expect(page.getByText(term, { exact: false })).toBeVisible({
timeout: 5000,
});
}
// Test interaction with the flow by adding a specific query
// Click the input field and type a query
await inputPlaceholder.click();
await page.keyboard.type("Summarize the total expenses from last month");
await page.keyboard.press("Enter");
// Wait for response to the specific query
await expect(page.getByText("Search Results Summary")).toBeVisible({
timeout: 20000,
});
// Verify that expense summary information appears in the response
await expect(page.getByText("expenses", { exact: false })).toBeVisible({
timeout: 10000,
});
// Test error handling - invalid query
await page.keyboard.type("xyz123$%^NonSensicalQuery");
await page.keyboard.press("Enter");
// Wait for the response, which should still show search results or appropriate message
await expect(
page
.getByText("Search Results", { exact: false })
.or(page.getByText("no relevant", { exact: false })),
).toBeVisible({ timeout: 20000 });
// Cleanup - Reset the session
await page.getByTestId("side_nav_options_all-templates").click();
},
);