Skip to content

Commit b229700

Browse files
author
APIEngineering
committed
Add Hybrid Search
A new search endpoint and ui section that combines full text search with vector search using Reciprocal rank fusion
1 parent 27ba464 commit b229700

File tree

11 files changed

+370
-40
lines changed

11 files changed

+370
-40
lines changed

builder/partnerproduct/src/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ vector_store:
1414
numCandidates: 150
1515
minScore: 0.1
1616
vectorSearchIndexName: 'vector_index'
17+
textSearchIndexName: 'text_index'
1718
llms:
1819
class_name: Fireworks
1920
model_name: 'accounts/fireworks/models/mixtral-8x22b-instruct'

builder/partnerproduct/src/loader.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ try {
2626
chunksAdded += chunks.entriesAdded;
2727
});
2828
}
29-
29+
3030
if (chunksAdded > 0) {
3131
console.log(`\n Total documents added : ${chunksAdded} `)
3232
await llmApplication.createVectorIndex();
33+
await llmApplication.createTextIndex();
3334
}
3435
else {
3536
console.log("\n-- Data not inserted, please retry --")

builder/partnerproduct/src/semantic-search.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const app = express();
1414
const port = 9001;
1515

1616
app.use(express.json());
17-
app.use(cors());
17+
app.use(cors());
1818

1919
const llmApplication = await new RAGApplicationBuilder()
2020
.setModel(getModelClass())
@@ -42,6 +42,30 @@ app.get('/semantic-search', async (req: Request, res: Response) => {
4242
}
4343
});
4444

45+
app.get('/hybrid-search', async (req: Request, res: Response) => {
46+
try {
47+
const userQuery = asString(req.query.query);
48+
const vectorWeight = asFloat(req.query.vectorWeight ?? 0.5);
49+
const fullTextWeight = asFloat(req.query.fullTextWeight ?? 0.5);
50+
51+
if (!userQuery) {
52+
return res.status(400).send('Query is required');
53+
}
54+
55+
llmApplication.hybridQuery(userQuery, vectorWeight, fullTextWeight).then((result) => {
56+
console.log('Result:', result);
57+
res.send(result);
58+
});
59+
60+
} catch (error) {
61+
console.error('Error during hybrid vector search:', error);
62+
res.status(500).send('An error occurred while processing your request.');
63+
}
64+
});
65+
4566
app.listen(port, () => {
4667
console.log(`Server is running on http://localhost:${port}`);
47-
});
68+
});
69+
70+
function asString(value: any): string { return typeof value !== 'undefined' ? value.toString() : ''; }
71+
function asFloat(value: any): number { return typeof value !== 'undefined' ? parseFloat(value.toString()) || 0 : 0; }

builder/partnerproduct/ui/package-lock.json

Lines changed: 117 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

builder/partnerproduct/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"@leafygreen-ui/card": "^11.0.0",
77
"@leafygreen-ui/icon": "^12.6.0",
88
"@leafygreen-ui/loading-indicator": "^2.0.12",
9+
"@leafygreen-ui/number-input": "^2.2.1",
910
"@leafygreen-ui/search-input": "^3.1.2",
1011
"@leafygreen-ui/side-nav": "^14.1.3",
1112
"@leafygreen-ui/tabs": "^13.0.1",

builder/partnerproduct/ui/src/App.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,19 @@ function App() {
4949
Vector Search
5050
</SideNavItem>
5151
</Link>
52+
<Link onClick={handleTabClick(3)} className="link" to="/hybrid-search">
53+
<SideNavItem active={activeTab === 3} glyph={<Icon glyph="Wizard" />} >
54+
Hybrid Search
55+
</SideNavItem>
56+
</Link>
5257
</SideNavGroup>
5358
</SideNav>
5459
<div className="main-content">
5560
<Routes>
5661
<Route path="/" element={<Hero />} />
5762
<Route path="/rag-chatbot" element={<ChatModule />} />
5863
<Route path="/search" element={<Search />} />
64+
<Route path="/hybrid-search" element={<Search hybrid="true" />} />
5965
</Routes>
6066
</div>
6167
</div>
@@ -64,4 +70,4 @@ function App() {
6470
);
6571
}
6672

67-
export default App;
73+
export default App;

builder/partnerproduct/ui/src/modules/search/search.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@ import Card from '@leafygreen-ui/card';
66
import { PageLoader, Spinner } from "@leafygreen-ui/loading-indicator";
77
import './search.css';
88
import { H1, H2 } from '@leafygreen-ui/typography';
9+
import { NumberInput } from '@leafygreen-ui/number-input';
910

10-
function Search() {
11+
function Search({ hybrid }) {
12+
const isHybridSearch = hybrid
1113
const [searchValue, setSearchValue] = useState('');
14+
const [vectorWeight, setVectorWeight] = useState(0.5);
15+
const [textWeight, setTextWeight] = useState(0.5);
1216
const [response, setResponse] = useState([]);
1317
const [loading, setLoading] = useState(false);
1418

1519
const handleInputChange = (event) => {
1620
setSearchValue(event.target.value);
1721
};
1822

23+
const searchEndpoint = `http://localhost:9001/${isHybridSearch ? 'hybrid-search' : 'semantic-search'}`;
24+
1925
const searchResults = () => {
2026
console.log(searchValue);
2127
setLoading(true);
2228
const query = encodeURIComponent(searchValue);
23-
fetch(`http://localhost:9001/semantic-search?query=${query}`, {
29+
fetch(`${searchEndpoint}?query=${query}&vectorWeight=${vectorWeight}&fullTextWeight=${textWeight}`, {
2430
method: 'GET',
2531
headers: {
2632
'Content-Type': 'application/json'
@@ -38,20 +44,37 @@ function Search() {
3844

3945
return (
4046
<div className="search-bar">
41-
<H1>Vector Search</H1>
47+
<H1>{ isHybridSearch ? "Hybrid Search" : "Vector Search"}</H1>
4248
<header className="search-header">
4349
<SearchInput
4450
value={searchValue}
4551
onChange={handleInputChange}
4652
onSubmit={searchResults}
4753
/>
54+
{ isHybridSearch && <>
55+
<NumberInput
56+
label='Vector Weight'
57+
description='Affects the weighted reciprocal rank'
58+
darkMode="true"
59+
value={vectorWeight}
60+
onChange={(e) => setVectorWeight(e.target.value)}
61+
/>
62+
<NumberInput
63+
label='Text Weight'
64+
description='Affects the weighted reciprocal rank'
65+
darkMode="true"
66+
value={textWeight}
67+
onChange={(e) => setTextWeight(e.target.value)}
68+
/>
69+
</> }
4870
</header>
4971
<div className='results'>
5072
<>
5173
{loading ? <PageLoader /> : <div>
5274
{response.map((item, index) => (
5375
<Card key={index} className="card-styles" as="article">
5476
<h3>Score: {item.score}</h3>
77+
{ isHybridSearch && <pre>Text Score: {item.fts_score}, Vector Score: {item.vs_score}</pre> }
5578
<p><strong>Content:</strong> {item.pageContent}</p>
5679
<div>
5780
<h4>Metadata:</h4>
@@ -66,4 +89,4 @@ function Search() {
6689
);
6790
}
6891

69-
export default Search;
92+
export default Search;

0 commit comments

Comments
 (0)