feat(tools): New Qdrant Tool (#644)

* feat: Qdrant tool

Signed-off-by: Anush008 <anushshetty90@gmail.com>

* docs: Updates

Signed-off-by: Anush008 <anushshetty90@gmail.com>

* Apply suggestions from code review

Signed-off-by: Anush008 <anushshetty90@gmail.com>

* chore: Updated placeholder text

Signed-off-by: Anush008 <anushshetty90@gmail.com>

* chore: Post merge updates

Signed-off-by: Anush008 <anushshetty90@gmail.com>

* chore:  visibility to 'user-only'

Signed-off-by: Anush008 <anushshetty90@gmail.com>

---------

Signed-off-by: Anush008 <anushshetty90@gmail.com>
This commit is contained in:
Anush
2025-07-23 01:18:30 +05:30
committed by GitHub
parent f5a64f400e
commit 31d909bb82
14 changed files with 876 additions and 1 deletions

View File

@@ -34,6 +34,7 @@
"outlook",
"perplexity",
"pinecone",
"qdrant",
"reddit",
"s3",
"schedule",

View File

@@ -0,0 +1,176 @@
---
title: Qdrant
description: Use Qdrant vector database
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="qdrant"
color="#1A223F"
icon={true}
iconSvg={`<svg className="block-icon" fill='none' viewBox='0 0 49 56' xmlns='http://www.w3.org/2000/svg'>
<g clip-path='url(#b)'>
<path
d='m38.489 51.477-1.1167-30.787-2.0223-8.1167 13.498 1.429v37.242l-8.2456 4.7589-2.1138-4.5259z'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m48.847 14-8.2457 4.7622-17.016-3.7326-19.917 8.1094-3.3183-9.139 12.122-7 12.126-7 12.123 7 12.126 7z'
clip-rule='evenodd'
fill='#7589BE'
fill-rule='evenodd'
/>
<path
d='m0.34961 13.999 8.2457 4.7622 4.7798 14.215 16.139 12.913-4.9158 10.109-12.126-7.0004-12.123-7v-28z'
clip-rule='evenodd'
fill='#B2BFE8'
fill-rule='evenodd'
/>
<path
d='m30.066 38.421-5.4666 8.059v9.5207l7.757-4.4756 3.9968-5.9681'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m24.602 36.962-7.7603-13.436 1.6715-4.4531 6.3544-3.0809 7.488 7.5343-7.7536 13.436z'
clip-rule='evenodd'
fill='#7589BE'
fill-rule='evenodd'
/>
<path
d='m16.843 23.525 7.7569 4.4756v8.9585l-7.1741 0.3087-4.3397-5.5412 3.7569-8.2016z'
clip-rule='evenodd'
fill='#B2BFE8'
fill-rule='evenodd'
/>
<path
d='m24.6 28 7.757-4.4752 5.2792 8.7903-6.3886 5.2784-6.6476-0.6346v-8.9589z'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m32.355 51.524 8.2457 4.476v-37.238l-8.0032-4.6189-7.9995-4.6189-8.0031 4.6189-7.9995 4.6189v18.479l7.9995 4.6189 8.0031 4.6193 7.757-4.4797v9.5244zm0-19.045-7.757 4.4793-7.7569-4.4793v-8.9549l7.7569-4.4792 7.757 4.4792v8.9549z'
clip-rule='evenodd'
fill='#DC244C'
fill-rule='evenodd'
/>
<path d='m24.603 46.483v-9.5222l-7.7166-4.4411v9.5064l7.7166 4.4569z' fill='url(#a)' />
</g>
<defs>
<linearGradient
id='a'
x1='23.18'
x2='15.491'
y1='38.781'
y2='38.781'
gradientUnits='userSpaceOnUse'
>
<stop stop-color='#FF3364' offset='0' />
<stop stop-color='#C91540' stop-opacity='0' offset='1' />
</linearGradient>
<clipPath id='b'>
<rect transform='translate(.34961)' fill='#fff' />
</clipPath>
</defs>
</svg>`}
/>
## Usage Instructions
Store, search, and retrieve vector embeddings using Qdrant. Perform semantic similarity searches and manage your vector collections.
## Tools
### `qdrant_upsert_points`
Insert or update points in a Qdrant collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | Yes | Qdrant base URL |
| `apiKey` | string | No | Qdrant API key \(optional\) |
| `collection` | string | Yes | Collection name |
| `points` | array | Yes | Array of points to upsert |
#### Output
| Parameter | Type |
| --------- | ---- |
| `status` | string |
| `data` | string |
### `qdrant_search_vector`
Search for similar vectors in a Qdrant collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | Yes | Qdrant base URL |
| `apiKey` | string | No | Qdrant API key \(optional\) |
| `collection` | string | Yes | Collection name |
| `vector` | array | Yes | Vector to search for |
| `limit` | number | No | Number of results to return |
| `filter` | object | No | Filter to apply to the search |
| `with_payload` | boolean | No | Include payload in response |
| `with_vector` | boolean | No | Include vector in response |
#### Output
| Parameter | Type |
| --------- | ---- |
| `data` | string |
| `status` | string |
### `qdrant_fetch_points`
Fetch points by ID from a Qdrant collection
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | Yes | Qdrant base URL |
| `apiKey` | string | No | Qdrant API key \(optional\) |
| `collection` | string | Yes | Collection name |
| `ids` | array | Yes | Array of point IDs to fetch |
| `with_payload` | boolean | No | Include payload in response |
| `with_vector` | boolean | No | Include vector in response |
#### Output
| Parameter | Type |
| --------- | ---- |
| `data` | string |
| `status` | string |
## Block Configuration
### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `operation` | string | Yes | Operation |
### Outputs
This block does not produce any outputs.
## Notes
- Category: `tools`
- Type: `qdrant`

View File

@@ -172,6 +172,9 @@ function Integrations() {
<div className='flex aspect-square h-16 w-16 items-center justify-center rounded-xl border border-[#353535] bg-[#242424] p-1 shadow-[0px_2px_6px_0px_rgba(126,_48,_252,_0.1)]'>
<Icons.pinecone />
</div>
<div className='flex aspect-square h-16 w-16 items-center justify-center rounded-xl border border-[#353535] bg-[#242424] p-1 shadow-[0px_2px_6px_0px_rgba(126,_48,_252,_0.1)]'>
<Icons.qdrant />
</div>
<div className='flex aspect-square h-16 w-16 items-center justify-center rounded-xl border border-[#353535] bg-[#242424] p-1 shadow-[0px_2px_6px_0px_rgba(126,_48,_252,_0.1)]'>
<Icons.slack />
</div>
@@ -290,6 +293,9 @@ function Integrations() {
<div className='flex aspect-square h-12 w-12 items-center justify-center rounded-xl border border-[#353535] bg-[#242424] p-1 shadow-[0px_2px_6px_0px_rgba(126,_48,_252,_0.1)]'>
<Icons.pinecone />
</div>
<div className='flex aspect-square h-12 w-12 items-center justify-center rounded-xl border border-[#353535] bg-[#242424] p-1 shadow-[0px_2px_6px_0px_rgba(126,_48,_252,_0.1)]'>
<Icons.qdrant />
</div>
<div className='flex aspect-square h-12 w-12 items-center justify-center rounded-xl border border-[#353535] bg-[#242424] p-1 shadow-[0px_2px_6px_0px_rgba(126,_48,_252,_0.1)]'>
<Icons.slack />
</div>
@@ -512,6 +518,77 @@ const Icons = {
/>
</svg>
),
qdrant: () => (
<svg width='48' height='48' fill='none' viewBox='0 0 49 56' xmlns='http://www.w3.org/2000/svg'>
<g clipPath='url(#b)'>
<path
d='m38.489 51.477-1.1167-30.787-2.0223-8.1167 13.498 1.429v37.242l-8.2456 4.7589-2.1138-4.5259z'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m48.847 14-8.2457 4.7622-17.016-3.7326-19.917 8.1094-3.3183-9.139 12.122-7 12.126-7 12.123 7 12.126 7z'
clip-rule='evenodd'
fill='#7589BE'
fill-rule='evenodd'
/>
<path
d='m0.34961 13.999 8.2457 4.7622 4.7798 14.215 16.139 12.913-4.9158 10.109-12.126-7.0004-12.123-7v-28z'
clip-rule='evenodd'
fill='#B2BFE8'
fill-rule='evenodd'
/>
<path
d='m30.066 38.421-5.4666 8.059v9.5207l7.757-4.4756 3.9968-5.9681'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m24.602 36.962-7.7603-13.436 1.6715-4.4531 6.3544-3.0809 7.488 7.5343-7.7536 13.436z'
clip-rule='evenodd'
fill='#7589BE'
fill-rule='evenodd'
/>
<path
d='m16.843 23.525 7.7569 4.4756v8.9585l-7.1741 0.3087-4.3397-5.5412 3.7569-8.2016z'
clip-rule='evenodd'
fill='#B2BFE8'
fill-rule='evenodd'
/>
<path
d='m24.6 28 7.757-4.4752 5.2792 8.7903-6.3886 5.2784-6.6476-0.6346v-8.9589z'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m32.355 51.524 8.2457 4.476v-37.238l-8.0032-4.6189-7.9995-4.6189-8.0031 4.6189-7.9995 4.6189v18.479l7.9995 4.6189 8.0031 4.6193 7.757-4.4797v9.5244zm0-19.045-7.757 4.4793-7.7569-4.4793v-8.9549l7.7569-4.4792 7.757 4.4792v8.9549z'
clip-rule='evenodd'
fill='#DC244C'
fill-rule='evenodd'
/>
<path d='m24.603 46.483v-9.5222l-7.7166-4.4411v9.5064l7.7166 4.4569z' fill='url(#a)' />
</g>
<defs>
<linearGradient
id='a'
x1='23.18'
x2='15.491'
y1='38.781'
y2='38.781'
gradientUnits='userSpaceOnUse'
>
<stop stop-color='#FF3364' offset='0' />
<stop stop-color='#C91540' stop-opacity='0' offset='1' />
</linearGradient>
<clipPath id='b'>
<rect transform='translate(.34961)' width='48.3' height='56' fill='#fff' />
</clipPath>
</defs>
</svg>
),
slack: () => (
<svg width='48' height='48' viewBox='0 0 48 48' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g clipPath='url(#clip0_82_6239)'>

View File

@@ -0,0 +1 @@
export * from './qdrant'

View File

@@ -0,0 +1,214 @@
import { QdrantIcon } from '@/components/icons'
import type { QdrantResponse } from '@/tools/qdrant/types'
import type { BlockConfig } from '../types'
export const QdrantBlock: BlockConfig<QdrantResponse> = {
type: 'qdrant',
name: 'Qdrant',
description: 'Use Qdrant vector database',
longDescription:
'Store, search, and retrieve vector embeddings using Qdrant. Perform semantic similarity searches and manage your vector collections.',
docsLink: 'https://qdrant.tech/documentation/',
category: 'tools',
bgColor: '#1A223F',
icon: QdrantIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Upsert', id: 'upsert' },
{ label: 'Search', id: 'search' },
{ label: 'Fetch', id: 'fetch' },
],
},
// Upsert fields
{
id: 'url',
title: 'Qdrant URL',
type: 'short-input',
layout: 'full',
placeholder: 'http://localhost:6333',
condition: { field: 'operation', value: 'upsert' },
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Your Qdrant API key (optional)',
password: true,
condition: { field: 'operation', value: 'upsert' },
},
{
id: 'collection',
title: 'Collection',
type: 'short-input',
layout: 'full',
placeholder: 'my-collection',
condition: { field: 'operation', value: 'upsert' },
},
{
id: 'points',
title: 'Points',
type: 'long-input',
layout: 'full',
placeholder: '[{"id": 1, "vector": [0.1, 0.2], "payload": {"category": "a"}}]',
condition: { field: 'operation', value: 'upsert' },
},
// Search fields
{
id: 'url',
title: 'Qdrant URL',
type: 'short-input',
layout: 'full',
placeholder: 'http://localhost:6333',
condition: { field: 'operation', value: 'search' },
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Your Qdrant API key (optional)',
password: true,
condition: { field: 'operation', value: 'search' },
},
{
id: 'collection',
title: 'Collection',
type: 'short-input',
layout: 'full',
placeholder: 'my-collection',
condition: { field: 'operation', value: 'search' },
},
{
id: 'vector',
title: 'Query Vector',
type: 'long-input',
layout: 'full',
placeholder: '[0.1, 0.2]',
condition: { field: 'operation', value: 'search' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: { field: 'operation', value: 'search' },
},
{
id: 'filter',
title: 'Filter',
type: 'long-input',
layout: 'full',
placeholder: '{"must":[{"key":"city","match":{"value":"London"}}]}',
condition: { field: 'operation', value: 'search' },
},
{
id: 'with_payload',
title: 'With Payload',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'search' },
},
{
id: 'with_vector',
title: 'With Vector',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'search' },
},
// Fetch fields
{
id: 'url',
title: 'Qdrant URL',
type: 'short-input',
layout: 'full',
placeholder: 'http://localhost:6333',
condition: { field: 'operation', value: 'fetch' },
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Your Qdrant API key (optional)',
password: true,
condition: { field: 'operation', value: 'fetch' },
},
{
id: 'collection',
title: 'Collection',
type: 'short-input',
layout: 'full',
placeholder: 'my-collection',
condition: { field: 'operation', value: 'fetch' },
},
{
id: 'ids',
title: 'IDs',
type: 'long-input',
layout: 'full',
placeholder: '["370446a3-310f-58db-8ce7-31db947c6c1e"]',
condition: { field: 'operation', value: 'fetch' },
},
{
id: 'with_payload',
title: 'With Payload',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'fetch' },
},
{
id: 'with_vector',
title: 'With Vector',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'fetch' },
},
],
tools: {
access: ['qdrant_upsert_points', 'qdrant_search_vector', 'qdrant_fetch_points'],
config: {
tool: (params: Record<string, any>) => {
switch (params.operation) {
case 'upsert':
return 'qdrant_upsert_points'
case 'search':
return 'qdrant_search_vector'
case 'fetch':
return 'qdrant_fetch_points'
default:
throw new Error('Invalid operation selected')
}
},
},
},
inputs: {
operation: { type: 'string', required: true },
url: { type: 'string', required: true },
apiKey: { type: 'string', required: false },
collection: { type: 'string', required: true },
points: { type: 'json', required: false },
vector: { type: 'json', required: false },
limit: { type: 'number', required: false },
filter: { type: 'json', required: false },
ids: { type: 'json', required: false },
with_payload: { type: 'boolean', required: false },
with_vector: { type: 'boolean', required: false },
},
outputs: {
matches: 'any',
upsertedCount: 'any',
data: 'any',
status: 'any',
},
}

View File

@@ -41,6 +41,7 @@ import { OpenAIBlock } from '@/blocks/blocks/openai'
import { OutlookBlock } from '@/blocks/blocks/outlook'
import { PerplexityBlock } from '@/blocks/blocks/perplexity'
import { PineconeBlock } from '@/blocks/blocks/pinecone'
import { QdrantBlock } from '@/blocks/blocks/qdrant'
import { RedditBlock } from '@/blocks/blocks/reddit'
import { ResponseBlock } from '@/blocks/blocks/response'
import { RouterBlock } from '@/blocks/blocks/router'
@@ -98,7 +99,6 @@ export const registry: Record<string, BlockConfig> = {
linear: LinearBlock,
linkup: LinkupBlock,
mem0: Mem0Block,
memory: MemoryBlock,
microsoft_excel: MicrosoftExcelBlock,
microsoft_teams: MicrosoftTeamsBlock,
mistral_parse: MistralParseBlock,
@@ -107,6 +107,8 @@ export const registry: Record<string, BlockConfig> = {
outlook: OutlookBlock,
perplexity: PerplexityBlock,
pinecone: PineconeBlock,
qdrant: QdrantBlock,
memory: MemoryBlock,
reddit: RedditBlock,
response: ResponseBlock,
router: RouterBlock,

View File

@@ -3033,3 +3033,77 @@ export function ScheduleIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function QdrantIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} fill='none' viewBox='0 0 49 56' xmlns='http://www.w3.org/2000/svg'>
<g clip-path='url(#b)'>
<path
d='m38.489 51.477-1.1167-30.787-2.0223-8.1167 13.498 1.429v37.242l-8.2456 4.7589-2.1138-4.5259z'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m48.847 14-8.2457 4.7622-17.016-3.7326-19.917 8.1094-3.3183-9.139 12.122-7 12.126-7 12.123 7 12.126 7z'
clip-rule='evenodd'
fill='#7589BE'
fill-rule='evenodd'
/>
<path
d='m0.34961 13.999 8.2457 4.7622 4.7798 14.215 16.139 12.913-4.9158 10.109-12.126-7.0004-12.123-7v-28z'
clip-rule='evenodd'
fill='#B2BFE8'
fill-rule='evenodd'
/>
<path
d='m30.066 38.421-5.4666 8.059v9.5207l7.757-4.4756 3.9968-5.9681'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m24.602 36.962-7.7603-13.436 1.6715-4.4531 6.3544-3.0809 7.488 7.5343-7.7536 13.436z'
clip-rule='evenodd'
fill='#7589BE'
fill-rule='evenodd'
/>
<path
d='m16.843 23.525 7.7569 4.4756v8.9585l-7.1741 0.3087-4.3397-5.5412 3.7569-8.2016z'
clip-rule='evenodd'
fill='#B2BFE8'
fill-rule='evenodd'
/>
<path
d='m24.6 28 7.757-4.4752 5.2792 8.7903-6.3886 5.2784-6.6476-0.6346v-8.9589z'
clip-rule='evenodd'
fill='#24386C'
fill-rule='evenodd'
/>
<path
d='m32.355 51.524 8.2457 4.476v-37.238l-8.0032-4.6189-7.9995-4.6189-8.0031 4.6189-7.9995 4.6189v18.479l7.9995 4.6189 8.0031 4.6193 7.757-4.4797v9.5244zm0-19.045-7.757 4.4793-7.7569-4.4793v-8.9549l7.7569-4.4792 7.757 4.4792v8.9549z'
clip-rule='evenodd'
fill='#DC244C'
fill-rule='evenodd'
/>
<path d='m24.603 46.483v-9.5222l-7.7166-4.4411v9.5064l7.7166 4.4569z' fill='url(#a)' />
</g>
<defs>
<linearGradient
id='a'
x1='23.18'
x2='15.491'
y1='38.781'
y2='38.781'
gradientUnits='userSpaceOnUse'
>
<stop stop-color='#FF3364' offset='0' />
<stop stop-color='#C91540' stop-opacity='0' offset='1' />
</linearGradient>
<clipPath id='b'>
<rect transform='translate(.34961)' width='48.3' height='56' fill='#fff' />
</clipPath>
</defs>
</svg>
)
}

View File

@@ -0,0 +1,89 @@
import type { ToolConfig } from '../types'
import type { QdrantFetchParams, QdrantResponse } from './types'
export const fetchPointsTool: ToolConfig<QdrantFetchParams, QdrantResponse> = {
id: 'qdrant_fetch_points',
name: 'Qdrant Fetch Points',
description: 'Fetch points by ID from a Qdrant collection',
version: '1.0',
params: {
url: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Qdrant base URL',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Qdrant API key (optional)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Collection name',
},
ids: {
type: 'array',
required: true,
visibility: 'user-only',
description: 'Array of point IDs to fetch',
},
with_payload: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include payload in response',
},
with_vector: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include vector in response',
},
},
request: {
method: 'POST',
url: (params) => `${params.url.replace(/\/$/, '')}/collections/${params.collection}/points`,
headers: (params) => ({
'Content-Type': 'application/json',
...(params.apiKey ? { 'api-key': params.apiKey } : {}),
}),
body: (params) => ({
ids: params.ids,
with_payload: params.with_payload,
with_vector: params.with_vector,
}),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
data: data.result,
status: data.status,
},
}
},
transformError: (error: any): string => {
if (error.error && typeof error.error === 'string') {
return error.error
}
if (error.status?.error) {
return error.status.error
}
if (error.message) {
return error.message
}
if (typeof error === 'string') {
return error
}
return 'Qdrant fetch points failed'
},
}

View File

@@ -0,0 +1,7 @@
import { fetchPointsTool } from './fetch_points'
import { searchVectorTool } from './search_vector'
import { upsertPointsTool } from './upsert_points'
export const qdrantUpsertTool = upsertPointsTool
export const qdrantSearchTool = searchVectorTool
export const qdrantFetchTool = fetchPointsTool

View File

@@ -0,0 +1,107 @@
import type { ToolConfig } from '../types'
import type { QdrantResponse, QdrantSearchParams } from './types'
export const searchVectorTool: ToolConfig<QdrantSearchParams, QdrantResponse> = {
id: 'qdrant_search_vector',
name: 'Qdrant Search Vector',
description: 'Search for similar vectors in a Qdrant collection',
version: '1.0',
params: {
url: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Qdrant base URL',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Qdrant API key (optional)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Collection name',
},
vector: {
type: 'array',
required: true,
visibility: 'user-only',
description: 'Vector to search for',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of results to return',
},
filter: {
type: 'object',
required: false,
visibility: 'user-only',
description: 'Filter to apply to the search',
},
with_payload: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include payload in response',
},
with_vector: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include vector in response',
},
},
request: {
method: 'POST',
url: (params) =>
`${params.url.replace(/\/$/, '')}/collections/${encodeURIComponent(params.collection)}/points/query`,
headers: (params) => ({
'Content-Type': 'application/json',
...(params.apiKey ? { 'api-key': params.apiKey } : {}),
}),
body: (params) => ({
query: params.vector,
limit: params.limit ? Number.parseInt(params.limit.toString()) : 10,
filter: params.filter,
with_payload: params.with_payload,
with_vector: params.with_vector,
}),
},
transformResponse: async (response) => {
if (!response.ok) {
throw new Error(`Qdrant search failed: ${response.statusText}`)
}
const data = await response.json()
return {
success: true,
output: {
data: data.result,
status: data.status,
},
}
},
transformError: (error: any): string => {
if (error.error && typeof error.error === 'string') {
return error.error
}
if (error.status?.error) {
return error.status.error
}
if (error.message) {
return error.message
}
if (typeof error === 'string') {
return error
}
return 'Qdrant search vector failed'
},
}

View File

@@ -0,0 +1,46 @@
import type { ToolResponse } from '../types'
export interface QdrantBaseParams {
url: string
apiKey?: string
collection: string
}
export interface QdrantVector {
id: string
vector: number[]
payload?: Record<string, any>
}
export interface QdrantUpsertParams extends QdrantBaseParams {
points: QdrantVector[]
}
export interface QdrantSearchParams extends QdrantBaseParams {
vector: number[]
limit?: number
filter?: Record<string, any>
with_payload?: boolean
with_vector?: boolean
}
export interface QdrantFetchParams extends QdrantBaseParams {
ids: string[]
with_payload?: boolean
with_vector?: boolean
}
export interface QdrantResponse extends ToolResponse {
output: {
result?: any
status?: string
matches?: Array<{
id: string
score: number
payload?: Record<string, any>
vector?: number[]
}>
upsertedCount?: number
data?: any
}
}

View File

@@ -0,0 +1,72 @@
import type { ToolConfig } from '../types'
import type { QdrantResponse, QdrantUpsertParams } from './types'
export const upsertPointsTool: ToolConfig<QdrantUpsertParams, QdrantResponse> = {
id: 'qdrant_upsert_points',
name: 'Qdrant Upsert Points',
description: 'Insert or update points in a Qdrant collection',
version: '1.0',
params: {
url: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Qdrant base URL',
},
apiKey: {
type: 'string',
required: false,
description: 'Qdrant API key (optional)',
},
collection: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Collection name',
},
points: {
type: 'array',
required: true,
visibility: 'user-only',
description: 'Array of points to upsert',
},
},
request: {
method: 'PUT',
url: (params) => `${params.url.replace(/\/$/, '')}/collections/${params.collection}/points`,
headers: (params) => ({
'Content-Type': 'application/json',
...(params.apiKey ? { 'api-key': params.apiKey } : {}),
}),
body: (params) => ({ points: params.points }),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: response.ok && data.status === 'ok',
output: {
status: data.status,
data: data.result,
},
}
},
transformError: (error: any): string => {
if (error.error && typeof error.error === 'string') {
return error.error
}
if (error.status?.error) {
return error.status.error
}
if (error.message) {
return error.message
}
if (typeof error === 'string') {
return error
}
return 'Qdrant upsert failed'
},
}

View File

@@ -0,0 +1,5 @@
export async function requestQdrant(url: string, path: string, options: RequestInit) {
const res = await fetch(url.replace(/\/$/, '') + path, options)
if (!res.ok) throw new Error(`Qdrant request failed: ${res.status}`)
return res.json()
}

View File

@@ -94,6 +94,7 @@ import {
pineconeSearchVectorTool,
pineconeUpsertTextTool,
} from '@/tools/pinecone'
import { qdrantFetchTool, qdrantSearchTool, qdrantUpsertTool } from '@/tools/qdrant'
import { redditGetCommentsTool, redditGetPostsTool, redditHotPostsTool } from '@/tools/reddit'
import { s3GetObjectTool } from '@/tools/s3'
import { searchTool as serperSearch } from '@/tools/serper'
@@ -261,4 +262,7 @@ export const tools: Record<string, ToolConfig> = {
wealthbox_write_task: wealthboxWriteTaskTool,
wealthbox_read_note: wealthboxReadNoteTool,
wealthbox_write_note: wealthboxWriteNoteTool,
qdrant_fetch_points: qdrantFetchTool,
qdrant_search_vector: qdrantSearchTool,
qdrant_upsert_points: qdrantUpsertTool,
}