crewAIInc/crewAI · error · ValueError

Invalid distance metric

Error message

Invalid distance metric: {metric}

What it means

During _run, after connecting, DB2VectorSearchTool uppercases self.distance_metric and checks membership in self._ALLOWED_METRICS; anything else raises ValueError(f"Invalid distance metric: {metric}"). The allowlist exists because the metric is interpolated into the SQL distance function (e.g. VECTOR_COSINE/COSINE family), so arbitrary strings are both invalid and unsafe. The connection is already open when this fires, but no query has run.

Solutions

  1. Use one of the allowed metric strings — check DB2VectorSearchTool._ALLOWED_METRICS for the installed version; typically 'COSINE', 'EUCLIDEAN', 'DOT' style names (e.g. distance_metric='COSINE').
  2. Print the allowlist once: print(DB2VectorSearchTool._ALLOWED_METRICS) to see exact accepted values.
  3. Guard configuration by validating distance_metric.upper() in _ALLOWED_METRICS before constructing the tool.

Example fix

# before
tool = DB2VectorSearchTool(table_name='docs', vector_column='embedding', return_columns=['id'], distance_metric='manhattan')

# after
tool = DB2VectorSearchTool(table_name='docs', vector_column='embedding', return_columns=['id'], distance_metric='EUCLIDEAN')
Defensive patterns

Strategy: validation

Validate before calling

metric = (distance_metric or 'COSINE').strip().upper()
if metric not in DB2VectorSearchTool._ALLOWED_METRICS:
    raise ValueError(f'Invalid distance metric: {metric}; allowed: {DB2VectorSearchTool._ALLOWED_METRICS}')
tool = DB2VectorSearchTool(..., distance_metric=metric)

Type guard

def is_allowed_metric(value: object) -> bool:
    return isinstance(value, str) and value.strip().upper() in DB2VectorSearchTool._ALLOWED_METRICS

Try / catch

try:
    tool._run(query_vector=vec, query_text=q)
except ValueError as e:
    if 'Invalid distance metric' in str(e):
        tool.distance_metric = 'COSINE'  # fall back to default and retry
        tool._run(query_vector=vec, query_text=q)
    else:
        raise

Prevention

When it happens

Trigger: DB2VectorSearchTool(..., distance_metric='manhattan') or 'dot' or 'euclidian' (typo) or 'cosine distance'; passing the metric with extra whitespace; assuming an abbreviation like 'l2' is supported.

Common situations: Porting code from another vector DB (pgvector, Pinecone) whose metric names differ; LLM-configured tools guessing metric names; typos and case variants (case is actually normalized, so only the name itself matters).

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/26a34bc749d47613. Report an issue: GitHub.

Appendix: source

Thrown at lib/crewai-tools/src/crewai_tools/tools/db2_search_tool/db2_search_tool.py:293

                indent=2,
            )

        try:
            query_vector = self._generate_embedding(query)

            # Explicit Connection Handling
            try:
                self._connect()
            except Exception as e:
                self._disconnect()  # Clean up any partial connection
                return json.dumps(
                    {"success": False, "error": f"Failed to connect to DB2: {e!s}"}
                )

            # Validate Metric
            metric = self.distance_metric.upper()
            if metric not in self._ALLOWED_METRICS:
                raise ValueError(f"Invalid distance metric: {metric}")

            # Validate Identifiers
            table = self._validate_identifier(self.table_name, allow_period=True)
            v_col = self._validate_identifier(self.vector_column)
            ret_cols = [self._validate_identifier(c) for c in self.return_columns]

            vector_dimension = len(query_vector)
            vector_string = str(query_vector)

            filter_clause = ""
            params = [vector_string]  # The vector string for the CLOB cast

            if filter_by and filter_value is not None:
                f_col = self._validate_identifier(filter_by)
                filter_clause = f"WHERE {f_col} = ?"
                params.append(filter_value)

            # DYNAMIC COLUMN SELECTION

View on GitHub (pinned to 754d7323be)