Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener
# AZURE_ANTHROPIC_API_KEY= # Azure Anthropic API key
# AZURE_ANTHROPIC_API_VERSION= # Azure Anthropic API version (e.g., 2023-06-01)
# NEXT_PUBLIC_AZURE_CONFIGURED=true # Set when Azure credentials are pre-configured above. Hides endpoint/key/version fields in Agent block UI.
# COHERE_API_KEY= # Cohere API key for the Knowledge block reranker (rerank-v4.0-pro/-fast, rerank-v3.5). Alternatively set COHERE_API_KEY_1/2/3 for rotation.
# NEXT_PUBLIC_COHERE_CONFIGURED=true # Set when COHERE_API_KEY (or rotation keys) are pre-configured above. Hides the Cohere API Key field on the Knowledge block UI.

# Admin API (Optional - for self-hosted GitOps)
# ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import.
Expand Down
25 changes: 21 additions & 4 deletions apps/sim/app/api/knowledge/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,21 @@ export const POST = withRouteHandler(async (request: NextRequest) => {

const hasFilters = structuredFilters && structuredFilters.length > 0

/** Oversample candidates when reranking so the reranker has more to choose from.
* Cap at 100 to bound Cohere request cost (1 search unit = ≤100 docs). */
const candidateTopK = useReranker ? Math.min(100, validatedData.topK * 4) : validatedData.topK
/** Oversample vector results when reranking so the reranker has more to choose from.
* Cap at 100 to bound Cohere request cost (1 search unit = ≤100 docs). When the caller
* supplies `rerankerInputCount`, honor it but never let it drop below `topK`
* (which would defeat the purpose) or exceed 100 (which would split into >1 search units). */
const rawInputCount = validatedData.rerankerInputCount
if (useReranker && rawInputCount !== undefined && rawInputCount < validatedData.topK) {
logger.warn(
`[${requestId}] rerankerInputCount (${rawInputCount}) is below topK (${validatedData.topK}); raising to topK`
)
}
const candidateTopK = useReranker
? rawInputCount !== undefined
? Math.min(100, Math.max(validatedData.topK, rawInputCount))
: Math.min(100, validatedData.topK * 4)
: validatedData.topK
Comment thread
waleedlatif1 marked this conversation as resolved.

if (!hasQuery && hasFilters) {
results = await handleTagOnlySearch({
Expand Down Expand Up @@ -300,7 +312,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
const { results: ranked, isBYOK } = await rerank(
validatedData.query!,
results.map((r) => ({ id: r.id, text: r.content })),
{ model: rerankerModel, topN: validatedData.topK, workspaceId }
{
model: rerankerModel,
topN: validatedData.topK,
workspaceId,
apiKey: validatedData.rerankerApiKey,
}
)
rerankBilled = true
rerankIsBYOK = isBYOK
Expand Down
28 changes: 28 additions & 0 deletions apps/sim/blocks/blocks/knowledge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PackageSearchIcon } from '@/components/icons'
import { DEFAULT_RERANKER_MODEL, SUPPORTED_RERANKER_MODELS } from '@/lib/knowledge/reranker-models'
import type { BlockConfig } from '@/blocks/types'
import { getCohereRerankerApiKeyCondition } from '@/blocks/utils'

export const KnowledgeBlock: BlockConfig = {
type: 'knowledge',
Expand Down Expand Up @@ -105,6 +106,28 @@ export const KnowledgeBlock: BlockConfig = {
and: { field: 'rerankerEnabled', value: true },
},
},
{
id: 'rerankerInputCount',
title: 'Documents Sent to Reranker',
type: 'short-input',
placeholder: 'Auto (4× results, capped at 100)',
mode: 'advanced',
condition: {
field: 'operation',
value: 'search',
and: { field: 'rerankerEnabled', value: true },
},
},
{
id: 'apiKey',
title: 'Cohere API Key',
type: 'short-input',
placeholder: 'Enter your Cohere API key',
password: true,
connectionDroppable: false,
required: true,
Comment thread
waleedlatif1 marked this conversation as resolved.
condition: getCohereRerankerApiKeyCondition(),
},
Comment thread
waleedlatif1 marked this conversation as resolved.

// --- List Documents ---
{
Expand Down Expand Up @@ -419,6 +442,11 @@ export const KnowledgeBlock: BlockConfig = {
tagFilters: { type: 'string', description: 'Tag filter criteria' },
rerankerEnabled: { type: 'boolean', description: 'Apply Cohere reranking to search results' },
rerankerModel: { type: 'string', description: 'Cohere rerank model identifier' },
rerankerInputCount: {
type: 'number',
description: 'Number of vector results sent to the Cohere reranker (1–100)',
},
apiKey: { type: 'string', description: 'Cohere API key (self-hosted only)' },
documentTags: { type: 'string', description: 'Document tags' },
chunkSearch: { type: 'string', description: 'Search filter for chunks' },
chunkEnabledFilter: { type: 'string', description: 'Filter chunks by enabled status' },
Expand Down
28 changes: 27 additions & 1 deletion apps/sim/blocks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { toError } from '@sim/utils/errors'
import { isAzureConfigured, isHosted, isOllamaConfigured } from '@/lib/core/config/feature-flags'
import {
isAzureConfigured,
isCohereConfigured,
isHosted,
isOllamaConfigured,
} from '@/lib/core/config/feature-flags'
import { getScopesForService } from '@/lib/oauth/utils'
import { buildCanonicalIndex } from '@/lib/workflows/subblocks/visibility'
import type { BlockOutput, OutputFieldDefinition, SubBlockConfig } from '@/blocks/types'
Expand Down Expand Up @@ -184,6 +189,27 @@ export function getApiKeyCondition() {
}
}

/**
* Visibility condition for the Cohere reranker API key field on the Knowledge block.
* Hidden on hosted Sim (platform supplies the key via workspace BYOK or rotating env keys)
* and on self-hosted deployments that have set `NEXT_PUBLIC_COHERE_CONFIGURED=true` to
* indicate `COHERE_API_KEY` is pre-configured server-side. Otherwise shown (and required)
* whenever reranking is enabled for a search operation, mirroring the agent block's
* `getApiKeyCondition` pattern.
*/
export function getCohereRerankerApiKeyCondition() {
return () => {
if (isHosted || isCohereConfigured) {
return { field: 'operation', value: '__never_show__' }
}
return {
field: 'operation',
value: 'search',
and: { field: 'rerankerEnabled', value: true },
}
}
}

/**
* Returns the standard provider credential subblocks used by LLM-based blocks.
* This includes: Vertex AI OAuth, API Key, Azure (OpenAI + Anthropic), Vertex AI config, and Bedrock config.
Expand Down
18 changes: 18 additions & 0 deletions apps/sim/lib/api/contracts/knowledge/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ export const knowledgeSearchBodySchema = z
.transform((val) => val || undefined),
rerankerEnabled: z.boolean().optional().default(false),
rerankerModel: rerankerModelSchema.optional().default(DEFAULT_RERANKER_MODEL),
/**
* Number of vector results sent to Cohere as the documents array for reranking. Capped at 100
* so each rerank call stays within a single Cohere search unit (1 query × ≤100 docs); see
* `RERANK_MODEL_PRICING` in `providers/models.ts`.
*/
rerankerInputCount: z
.number()
.int('rerankerInputCount must be an integer')
.min(1, 'rerankerInputCount must be at least 1')
.max(100, 'rerankerInputCount cannot exceed 100')
.optional()
.nullable()
.transform((val) => val ?? undefined),
rerankerApiKey: z
.string()
.optional()
.nullable()
.transform((val) => val || undefined),
Comment thread
waleedlatif1 marked this conversation as resolved.
})
.refine(
(data) => {
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ export const env = createEnv({
NEXT_PUBLIC_E2B_ENABLED: z.string().optional(),
NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: z.string().optional(), // Hide Bedrock credential fields when deployment uses AWS default credential chain (IAM roles, instance profiles, ECS task roles, IRSA)
NEXT_PUBLIC_AZURE_CONFIGURED: z.string().optional(), // Hide Azure credential fields when endpoint/key/version are pre-configured server-side
NEXT_PUBLIC_COHERE_CONFIGURED: z.string().optional(), // Hide Cohere API key field on Knowledge block when COHERE_API_KEY is pre-configured server-side
NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: z.string().optional(),
NEXT_PUBLIC_ENABLE_PLAYGROUND: z.string().optional(), // Enable component playground at /playground
NEXT_PUBLIC_DOCUMENTATION_URL: z.string().url().optional(), // Custom documentation URL
Expand Down Expand Up @@ -496,6 +497,7 @@ export const env = createEnv({
NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED,
NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: process.env.NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS,
NEXT_PUBLIC_AZURE_CONFIGURED: process.env.NEXT_PUBLIC_AZURE_CONFIGURED,
NEXT_PUBLIC_COHERE_CONFIGURED: process.env.NEXT_PUBLIC_COHERE_CONFIGURED,
NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: process.env.NEXT_PUBLIC_COPILOT_TRAINING_ENABLED,
NEXT_PUBLIC_ENABLE_PLAYGROUND: process.env.NEXT_PUBLIC_ENABLE_PLAYGROUND,
NEXT_PUBLIC_POSTHOG_ENABLED: process.env.NEXT_PUBLIC_POSTHOG_ENABLED,
Expand Down
8 changes: 8 additions & 0 deletions apps/sim/lib/core/config/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ export const isOllamaConfigured = Boolean(env.OLLAMA_URL)
*/
export const isAzureConfigured = isTruthy(getEnv('NEXT_PUBLIC_AZURE_CONFIGURED'))

/**
* Whether a Cohere API key is pre-configured server-side for the Knowledge block reranker
* (`COHERE_API_KEY` or `COHERE_API_KEY_1/2/3`). When true, the Cohere API Key field is hidden
* in the Knowledge block UI.
* Set NEXT_PUBLIC_COHERE_CONFIGURED=true in self-hosted deployments that ship a Cohere key.
*/
export const isCohereConfigured = isTruthy(getEnv('NEXT_PUBLIC_COHERE_CONFIGURED'))

/**
* Are invitations disabled globally
* When true, workspace invitations are disabled for all users
Expand Down
35 changes: 33 additions & 2 deletions apps/sim/lib/knowledge/reranker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import { getBYOKKey } from '@/lib/api-key/byok'
import { getRotatingApiKey } from '@/lib/core/config/api-keys'
import { env } from '@/lib/core/config/env'
import { isHosted } from '@/lib/core/config/feature-flags'
import { isRetryableError, retryWithExponentialBackoff } from '@/lib/knowledge/documents/utils'
import {
DEFAULT_RERANKER_MODEL,
Expand Down Expand Up @@ -56,8 +57,18 @@ class RerankAPIError extends Error {
}

async function resolveCohereKey(
workspaceId?: string | null
workspaceId?: string | null,
userApiKey?: string
): Promise<{ apiKey: string; isBYOK: boolean }> {
/**
* Mirrors the agent block hosted-key pattern (`injectHostedKeyIfNeeded`):
* on self-hosted the user-supplied key from the block field flows through
* unchanged; on hosted Sim we always source the key from workspace BYOK or
* platform env, so any user-supplied value is ignored.
*/
if (!isHosted && userApiKey) {
return { apiKey: userApiKey, isBYOK: false }
}
if (workspaceId) {
const byokResult = await getBYOKKey(workspaceId, 'cohere')
if (byokResult) {
Expand All @@ -77,8 +88,19 @@ async function resolveCohereKey(
}
}

/**
* Subset of Cohere v2/rerank response fields we read.
* Reference: https://docs.cohere.com/v2/reference/rerank
* - `results[].index` maps back to the position in the documents we sent.
* - `results[].relevance_score` is normalized 0–1.
* - `meta.warnings` is documented as an array of strings; we surface them in logs
* so issues like document truncation don't disappear silently.
*/
interface CohereRerankResponse {
results: Array<{ index: number; relevance_score: number }>
meta?: {
warnings?: string[]
}
}

/**
Expand All @@ -92,6 +114,8 @@ export async function rerank<T extends RerankItem>(
model: string
topN?: number
workspaceId?: string | null
/** User-supplied Cohere key from the Knowledge block field. Honored only on self-hosted. */
apiKey?: string
}
): Promise<RerankResponse<T>> {
if (items.length === 0) return { results: [], isBYOK: false }
Expand All @@ -100,7 +124,7 @@ export async function rerank<T extends RerankItem>(
throw new Error(`Unsupported reranker model: ${options.model}`)
}

const { apiKey, isBYOK } = await resolveCohereKey(options.workspaceId)
const { apiKey, isBYOK } = await resolveCohereKey(options.workspaceId, options.apiKey)
const cappedItems =
items.length > MAX_DOCUMENTS_PER_RERANK ? items.slice(0, MAX_DOCUMENTS_PER_RERANK) : items
if (items.length > MAX_DOCUMENTS_PER_RERANK) {
Expand Down Expand Up @@ -151,6 +175,13 @@ export async function rerank<T extends RerankItem>(
}
)

if (response.meta?.warnings && response.meta.warnings.length > 0) {
logger.warn('Cohere rerank returned warnings', {
model: options.model,
warnings: response.meta.warnings,
})
}

return {
results: response.results
.filter((r) => r.index >= 0 && r.index < cappedItems.length)
Expand Down
31 changes: 30 additions & 1 deletion apps/sim/tools/knowledge/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ export const knowledgeSearchTool: ToolConfig<any, KnowledgeSearchResponse> = {
description:
'Cohere rerank model to use (one of: rerank-v4.0-pro, rerank-v4.0-fast, rerank-v3.5)',
},
rerankerInputCount: {
type: 'number',
required: false,
visibility: 'user-only',
description:
'Number of vector results sent to the Cohere reranker (1–100). Defaults to topK × 4 capped at 100.',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Cohere API key for reranker (self-hosted deployments only)',
},
},

schemaEnrichment: {
Expand Down Expand Up @@ -84,13 +97,29 @@ export const knowledgeSearchTool: ToolConfig<any, KnowledgeSearchResponse> = {
typeof params.rerankerModel === 'string' && params.rerankerModel.length > 0
? params.rerankerModel
: DEFAULT_RERANKER_MODEL
const rerankerApiKey =
typeof params.apiKey === 'string' && params.apiKey.length > 0 ? params.apiKey : undefined
const rawInputCount =
params.rerankerInputCount !== undefined &&
params.rerankerInputCount !== null &&
params.rerankerInputCount !== ''
? Number(params.rerankerInputCount)
: Number.NaN
const rerankerInputCount = Number.isFinite(rawInputCount)
? Math.max(1, Math.min(100, Math.floor(rawInputCount)))
: undefined
Comment thread
waleedlatif1 marked this conversation as resolved.

const requestBody = {
knowledgeBaseIds,
query: params.query,
topK: params.topK ? Math.max(1, Math.min(100, Number(params.topK))) : 10,
...(structuredFilters.length > 0 && { tagFilters: structuredFilters }),
...(rerankerEnabled && { rerankerEnabled: true, rerankerModel }),
...(rerankerEnabled && {
rerankerEnabled: true,
rerankerModel,
...(rerankerInputCount !== undefined && { rerankerInputCount }),
...(rerankerApiKey && { rerankerApiKey }),
}),
...(workflowId && { workflowId }),
}

Expand Down
6 changes: 6 additions & 0 deletions helm/sim/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ app:
# in the Agent block UI — users just pick an Azure model and run.
NEXT_PUBLIC_AZURE_CONFIGURED: "" # Set to "true" to hide Azure credential fields

# Cohere Reranker (Knowledge block)
# Set COHERE_API_KEY (or COHERE_API_KEY_1/2/3 for rotation) and NEXT_PUBLIC_COHERE_CONFIGURED=true
# to pre-configure the Cohere reranker server-side. When configured, the Cohere API Key field is
# hidden in the Knowledge block UI.
NEXT_PUBLIC_COHERE_CONFIGURED: "" # Set to "true" to hide the Cohere API Key field on the Knowledge block

# AWS S3 Cloud Storage Configuration (optional - for file storage)
# If configured, files will be stored in S3 instead of local storage
AWS_REGION: "" # AWS region (e.g., "us-east-1")
Expand Down
Loading