diff --git a/app/api/proxy/route.ts b/app/api/proxy/route.ts index 70558060c9..a16a33ad27 100644 --- a/app/api/proxy/route.ts +++ b/app/api/proxy/route.ts @@ -48,7 +48,16 @@ export async function POST(request: Request) { const method = params.method || defaultMethod || 'GET' const headers = headersFn ? headersFn(params) : {} const hasBody = method !== 'GET' && method !== 'HEAD' && !!bodyFn - const body = hasBody ? JSON.stringify(bodyFn!(params)) : undefined + + const bodyResult = bodyFn ? bodyFn(params) : undefined + + // Special handling for NDJSON content type + const isNDJSON = headers['Content-Type'] === 'application/x-ndjson' + const body = hasBody + ? isNDJSON && bodyResult + ? bodyResult.body + : JSON.stringify(bodyResult) + : undefined const externalResponse = await fetch(url, { method, headers, body }) diff --git a/blocks/blocks/pinecone.ts b/blocks/blocks/pinecone.ts index 2bd4443d9c..0650ac3aec 100644 --- a/blocks/blocks/pinecone.ts +++ b/blocks/blocks/pinecone.ts @@ -22,25 +22,8 @@ export const PineconeBlock: BlockConfig = { { label: 'Upsert Text', id: 'upsert_text' }, { label: 'Search Text', id: 'search_text' }, { label: 'Fetch Vectors', id: 'fetch' }, - { label: 'Delete Vectors', id: 'delete' }, ], }, - { - id: 'environment', - title: 'Environment', - type: 'short-input', - layout: 'full', - placeholder: 'gcp-starter', - condition: { field: 'operation', value: 'upsert_text' }, - }, - { - id: 'indexName', - title: 'Index Name', - type: 'short-input', - layout: 'full', - placeholder: 'my-index', - condition: { field: 'operation', value: 'upsert_text' }, - }, // Generate embeddings fields { id: 'model', @@ -63,12 +46,29 @@ export const PineconeBlock: BlockConfig = { condition: { field: 'operation', value: 'generate' }, }, // Upsert text fields + { + id: 'indexHost', + title: 'Index Host', + type: 'short-input', + layout: 'full', + placeholder: 'https://index-name-abc123.svc.project-id.pinecone.io', + condition: { field: 'operation', value: 'upsert_text' }, + }, + { + id: 'namespace', + title: 'Namespace', + type: 'short-input', + layout: 'full', + placeholder: 'Optional namespace', + condition: { field: 'operation', value: 'upsert_text' }, + }, { id: 'records', title: 'Records', type: 'long-input', layout: 'full', - placeholder: '[{"_id": "doc1", "text": "Your text here", "metadata": {"key": "value"}}]', + placeholder: + '{"_id": "rec1", "text": "Apple\'s first product, the Apple I, was released in 1976.", "category": "product"}\n{"_id": "rec2", "chunk_text": "Apples are a great source of dietary fiber.", "category": "nutrition"}', condition: { field: 'operation', value: 'upsert_text' }, }, // Search text fields @@ -88,15 +88,39 @@ export const PineconeBlock: BlockConfig = { placeholder: '10', condition: { field: 'operation', value: 'search_text' }, }, + { + id: 'fields', + title: 'Fields to Return', + type: 'long-input', + layout: 'full', + placeholder: '["category", "text"]', + condition: { field: 'operation', value: 'search_text' }, + }, { id: 'filter', title: 'Filter', type: 'long-input', layout: 'full', - placeholder: '{"key": "value"}', + placeholder: '{"category": "product"}', + condition: { field: 'operation', value: 'search_text' }, + }, + { + id: 'rerank', + title: 'Rerank Options', + type: 'long-input', + layout: 'full', + placeholder: '{"model": "bge-reranker-v2-m3", "rank_fields": ["text"], "top_n": 2}', condition: { field: 'operation', value: 'search_text' }, }, // Fetch fields + { + id: 'indexHost', + title: 'Index Host', + type: 'short-input', + layout: 'full', + placeholder: 'https://index-name-abc123.svc.project-id.pinecone.io', + condition: { field: 'operation', value: 'fetch' }, + }, { id: 'ids', title: 'Vector IDs', @@ -105,15 +129,15 @@ export const PineconeBlock: BlockConfig = { placeholder: '["vec1", "vec2"]', condition: { field: 'operation', value: 'fetch' }, }, - // Common fields { id: 'namespace', title: 'Namespace', type: 'short-input', layout: 'full', placeholder: 'Optional namespace', - condition: { field: 'operation', value: 'upsert_text' }, + condition: { field: 'operation', value: 'fetch' }, }, + // Common fields { id: 'apiKey', title: 'API Key', @@ -130,7 +154,6 @@ export const PineconeBlock: BlockConfig = { 'pinecone_upsert_text', 'pinecone_search_text', 'pinecone_fetch', - 'pinecone_delete', ], config: { tool: (params: Record) => { @@ -143,8 +166,6 @@ export const PineconeBlock: BlockConfig = { return 'pinecone_search_text' case 'fetch': return 'pinecone_fetch' - case 'delete': - return 'pinecone_delete' default: throw new Error('Invalid operation selected') } @@ -157,6 +178,7 @@ export const PineconeBlock: BlockConfig = { apiKey: { type: 'string', required: true }, environment: { type: 'string', required: false }, indexName: { type: 'string', required: false }, + indexHost: { type: 'string', required: false }, namespace: { type: 'string', required: false }, // Generate embeddings inputs model: { type: 'string', required: false }, @@ -167,11 +189,11 @@ export const PineconeBlock: BlockConfig = { // Search text inputs searchQuery: { type: 'string', required: false }, topK: { type: 'string', required: false }, + fields: { type: 'json', required: false }, filter: { type: 'json', required: false }, + rerank: { type: 'json', required: false }, // Fetch inputs ids: { type: 'json', required: false }, - // Delete inputs - deleteAll: { type: 'boolean', required: false }, }, outputs: { diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 1c9957fcbe..a776673eb2 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -33,6 +33,12 @@ import { cn } from '@/lib/utils' // This file is not typed correctly from shadcn, so we're disabling the type checker // @ts-nocheck +// This file is not typed correctly from shadcn, so we're disabling the type checker +// @ts-nocheck + +// This file is not typed correctly from shadcn, so we're disabling the type checker +// @ts-nocheck + const Command = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { diff --git a/tools/index.ts b/tools/index.ts index 5d5f66876b..3b8e3738ba 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -16,7 +16,6 @@ import { notionReadTool } from './notion/read' import { notionWriteTool } from './notion/write' import { chatTool as openAIChat } from './openai/chat' import { embeddingsTool as openAIEmbeddings } from './openai/embeddings' -import { deleteTool as pineconeDeleteTool } from './pinecone/delete' import { fetchTool as pineconeFetchTool } from './pinecone/fetch' import { generateEmbeddingsTool as pineconeGenerateEmbeddingsTool } from './pinecone/generate' import { searchTextTool as pineconeSearchTextTool } from './pinecone/searchText' @@ -65,7 +64,6 @@ export const tools: Record = { x_read: xRead, x_search: xSearch, x_user: xUser, - pinecone_delete: pineconeDeleteTool, pinecone_fetch: pineconeFetchTool, pinecone_generate_embeddings: pineconeGenerateEmbeddingsTool, pinecone_search_text: pineconeSearchTextTool, diff --git a/tools/pinecone/delete.ts b/tools/pinecone/delete.ts deleted file mode 100644 index d5049d1a2d..0000000000 --- a/tools/pinecone/delete.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ToolConfig } from '../types' -import { PineconeParams, PineconeResponse } from './types' - -export const deleteTool: ToolConfig = { - id: 'pinecone_delete', - name: 'Pinecone Delete', - description: 'Delete vectors from Pinecone index', - version: '1.0', - - params: { - apiKey: { type: 'string', required: true, description: 'Pinecone API key' }, - environment: { type: 'string', required: true, description: 'Pinecone environment' }, - indexName: { type: 'string', required: true, description: 'Name of the Pinecone index' }, - ids: { type: 'array', required: false, description: 'Array of vector IDs to delete' }, - deleteAll: { type: 'boolean', required: false, description: 'Delete all vectors in the index' }, - }, - - request: { - method: 'POST', - url: (params) => - `https://${params.indexName}-${params.environment}.svc.${params.environment}.pinecone.io/vectors/delete`, - headers: (params) => ({ - 'Api-Key': params.apiKey, - 'Content-Type': 'application/json', - }), - body: (params) => ({ - ids: params.ids, - deleteAll: params.deleteAll, - }), - }, - - transformResponse: async (response) => { - return { - success: true, - output: { - deletedCount: response.ok ? 1 : 0, // Pinecone doesn't return count, so we estimate - }, - } - }, - - transformError: (error) => `Pinecone delete failed: ${error.message}`, -} diff --git a/tools/pinecone/fetch.ts b/tools/pinecone/fetch.ts index 8eade5f036..9f0ed01ca5 100644 --- a/tools/pinecone/fetch.ts +++ b/tools/pinecone/fetch.ts @@ -1,5 +1,5 @@ import { ToolConfig } from '../types' -import { PineconeFetchParams, PineconeResponse } from './types' +import { PineconeFetchParams, PineconeResponse, PineconeVector } from './types' export const fetchTool: ToolConfig = { id: 'pinecone_fetch', @@ -8,17 +8,31 @@ export const fetchTool: ToolConfig = { version: '1.0', params: { - apiKey: { type: 'string', required: true, description: 'Pinecone API key' }, - environment: { type: 'string', required: true, description: 'Pinecone environment' }, - indexName: { type: 'string', required: true, description: 'Name of the Pinecone index' }, - ids: { type: 'array', required: true, description: 'Array of vector IDs to fetch' }, + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'Pinecone API key', + }, + indexHost: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'Full Pinecone index host URL', + }, + ids: { + type: 'array', + required: true, + requiredForToolCall: true, + description: 'Array of vector IDs to fetch', + }, namespace: { type: 'string', required: false, description: 'Namespace to fetch vectors from' }, }, request: { method: 'GET', url: (params) => { - const baseUrl = `https://${params.indexName}-${params.environment}.svc.${params.environment}.pinecone.io/vectors/fetch` + const baseUrl = `${params.indexHost}/vectors/fetch` const queryParams = new URLSearchParams() queryParams.append('ids', params.ids.join(',')) if (params.namespace) { @@ -34,15 +48,24 @@ export const fetchTool: ToolConfig = { transformResponse: async (response) => { const data = await response.json() + const vectors = data.vectors as Record + return { success: true, output: { - matches: Object.entries(data.vectors).map(([id, vector]: [string, any]) => ({ + matches: Object.entries(vectors).map(([id, vector]) => ({ id, - score: 1.0, // Fetch doesn't return scores, so we use 1.0 for exact matches values: vector.values, metadata: vector.metadata, + score: 1.0, // Fetch returns exact matches })), + data: Object.values(vectors).map((vector) => ({ + values: vector.values, + vector_type: 'dense' as const, + })), + usage: { + total_tokens: data.usage?.readUnits || 0, + }, }, } }, diff --git a/tools/pinecone/generate.ts b/tools/pinecone/generate.ts index 473d5ed986..9e91d8c671 100644 --- a/tools/pinecone/generate.ts +++ b/tools/pinecone/generate.ts @@ -11,7 +11,12 @@ export const generateEmbeddingsTool: ToolConfig< version: '1.0', params: { - apiKey: { type: 'string', required: true, description: 'Pinecone API key' }, + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'Pinecone API key', + }, model: { type: 'string', required: true, diff --git a/tools/pinecone/searchText.ts b/tools/pinecone/searchText.ts index a4900d830c..56f122b287 100644 --- a/tools/pinecone/searchText.ts +++ b/tools/pinecone/searchText.ts @@ -1,5 +1,5 @@ import { ToolConfig } from '../types' -import { PineconeResponse, PineconeSearchTextParams } from './types' +import { PineconeResponse, PineconeSearchHit, PineconeSearchTextParams } from './types' export const searchTextTool: ToolConfig = { id: 'pinecone_search_text', @@ -8,40 +8,68 @@ export const searchTextTool: ToolConfig - `https://${params.indexName}-${params.environment}.svc.${params.environment}.pinecone.io/records/namespaces/${params.namespace || ''}/search`, + url: (params) => `${params.indexHost}/records/namespaces/${params.namespace}/search`, headers: (params) => ({ 'Api-Key': params.apiKey, 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-Pinecone-API-Version': '2025-01', }), - body: (params) => ({ - query: params.query, - fields: params.fields, - rerank: params.rerank, - }), + body: (params) => { + // Format the query object + const query = { + inputs: { text: params.searchQuery }, + top_k: parseInt(params.topK || '10'), + } + + // Build the request body + const body: any = { + query, + } + + // Add optional parameters if provided + if (params.fields) { + body.fields = typeof params.fields === 'string' ? JSON.parse(params.fields) : params.fields + } + + if (params.filter) { + body.query.filter = + typeof params.filter === 'string' ? JSON.parse(params.filter) : params.filter + } + + if (params.rerank) { + body.rerank = typeof params.rerank === 'string' ? JSON.parse(params.rerank) : params.rerank + // If rerank query is not specified, use the search query + if (!body.rerank.query) { + body.rerank.query = { text: params.searchQuery } + } + } + + console.log('Search request body:', body) + return body + }, }, transformResponse: async (response) => { @@ -49,12 +77,16 @@ export const searchTextTool: ToolConfig ({ - id: match.id, - score: match.score, - metadata: match.metadata, - values: match.values, + matches: data.result.hits.map((hit: PineconeSearchHit) => ({ + id: hit._id, + score: hit._score, + metadata: hit.fields, })), + usage: { + total_tokens: data.usage.embed_total_tokens || 0, + read_units: data.usage.read_units, + rerank_units: data.usage.rerank_units, + }, }, } }, diff --git a/tools/pinecone/types.ts b/tools/pinecone/types.ts index c212cd0261..089709502f 100644 --- a/tools/pinecone/types.ts +++ b/tools/pinecone/types.ts @@ -2,9 +2,9 @@ import { ToolResponse } from '../types' // Base Pinecone params shared across all operations export interface PineconeBaseParams { + indexHost: string + namespace: string apiKey: string - environment: string - indexName: string } // Response types @@ -19,7 +19,6 @@ export interface PineconeResponse extends ToolResponse { output: { matches?: PineconeMatchResponse[] upsertedCount?: number - deletedCount?: number data?: Array<{ values: number[] vector_type: 'dense' | 'sparse' @@ -44,18 +43,19 @@ export interface PineconeGenerateEmbeddingsParams { } // Upsert Text +export interface PineconeUpsertTextRecord { + _id: string + chunk_text: string + category?: string + [key: string]: any +} + export interface PineconeUpsertTextParams extends PineconeBaseParams { - namespace?: string - records: { - _id: string - text: string - metadata?: Record - }[] + records: PineconeUpsertTextRecord | PineconeUpsertTextRecord[] } // Upsert Vectors export interface PineconeUpsertVectorsParams extends PineconeBaseParams { - namespace?: string vectors: { id: string values: number[] @@ -68,33 +68,75 @@ export interface PineconeUpsertVectorsParams extends PineconeBaseParams { } // Search Text -export interface PineconeSearchTextParams extends PineconeBaseParams { - namespace?: string - query: { - inputs: string - top_k: number - filter?: Record +export interface PineconeSearchQuery { + inputs?: { text: string } + vector?: { + values: number[] + sparse_values?: number[] + sparse_indices?: number[] } - fields?: string[] - rerank?: { - model: string - rank_fields: string[] - top_n?: number - parameters?: Record - query?: string + id?: string + top_k: number + filter?: Record +} + +export interface PineconeRerank { + model: string + rank_fields: string[] + top_n?: number + parameters?: Record + query?: { text: string } +} + +export interface PineconeSearchTextParams extends PineconeBaseParams { + searchQuery: string + topK?: string + fields?: string[] | string + filter?: Record | string + rerank?: PineconeRerank | string +} + +export interface PineconeSearchHit { + _id: string + _score: number + fields?: Record +} + +export interface PineconeSearchResponse { + result: { + hits: PineconeSearchHit[] + } + usage: { + read_units: number + embed_total_tokens?: number + rerank_units?: number } } // Fetch Vectors export interface PineconeFetchParams extends PineconeBaseParams { ids: string[] +} + +export interface PineconeVector { + id: string + values: number[] + metadata?: Record +} + +export interface PineconeUsage { + readUnits: number +} + +export interface PineconeFetchResponse { + vectors: Record namespace?: string + usage: PineconeUsage } export interface PineconeParams { apiKey: string - environment: string - indexName: string + indexHost: string operation: 'query' | 'upsert' | 'delete' // Query operation queryVector?: number[] @@ -107,7 +149,4 @@ export interface PineconeParams { values: number[] metadata?: Record }> - // Delete operation - ids?: string[] - deleteAll?: boolean } diff --git a/tools/pinecone/upsertText.ts b/tools/pinecone/upsertText.ts index 9ddff05b1a..240fd68070 100644 --- a/tools/pinecone/upsertText.ts +++ b/tools/pinecone/upsertText.ts @@ -1,5 +1,5 @@ import { ToolConfig } from '../types' -import { PineconeResponse, PineconeUpsertTextParams } from './types' +import { PineconeResponse, PineconeUpsertTextParams, PineconeUpsertTextRecord } from './types' export const upsertTextTool: ToolConfig = { id: 'pinecone_upsert_text', @@ -8,31 +8,70 @@ export const upsertTextTool: ToolConfig - `https://${params.indexName}-${params.environment}.svc.${params.environment}.pinecone.io/records/namespaces/${params.namespace || ''}/upsert`, + url: (params) => `${params.indexHost}/records/namespaces/${params.namespace}/upsert`, headers: (params) => ({ 'Api-Key': params.apiKey, - 'Content-Type': 'application/json', - }), - body: (params) => ({ - records: params.records, + 'Content-Type': 'application/x-ndjson', + 'X-Pinecone-API-Version': '2025-01', }), + body: (params) => { + // If records is a string, parse it line by line + let records: PineconeUpsertTextRecord[] + if (typeof params.records === 'string') { + // Split by newlines and parse each line + records = (params.records as string) + .split('\n') + .filter((line: string) => line.trim()) // Remove empty lines + .map((line: string) => { + // Clean and parse each line + const cleanJson = line.trim().replace(/'\\''/g, "'") + return JSON.parse(cleanJson) as PineconeUpsertTextRecord + }) + } else { + records = Array.isArray(params.records) ? params.records : [params.records] + } + + // Convert to NDJSON format + const ndjson = records.map((r: PineconeUpsertTextRecord) => JSON.stringify(r)).join('\n') + return { body: ndjson } + }, }, transformResponse: async (response) => { + // Handle empty response (201 Created) + if (response.status === 201) { + return { + success: true, + output: { + statusText: 'Created', + }, + } + } + + // Handle response with content const data = await response.json() return { success: true,