{"record":{"id":"6f36aadf1625eacf","repo":"crewAIInc/crewAI","slug":"filter-by-and-filter-value-must-be-provided-togeth","errorCode":null,"errorMessage":"filter_by and filter_value must be provided together.","messagePattern":"filter_by and filter_value must be provided together\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/db2_search_tool/db2_search_tool.py","lineNumber":58,"sourceCode":"        description=(\n            \"Column name used for metadata filtering. \"\n            \"Must be used together with filter_value.\"\n        ),\n    )\n\n    filter_value: Any | None = Field(\n        default=None,\n        description=(\n            \"Value used for metadata filtering. Must be used together with filter_by.\"\n        ),\n    )\n\n    @model_validator(mode=\"after\")\n    def _validate_filter_pair(self) -> DB2ToolSchema:\n        if self.filter_by is not None and not self.filter_by.strip():\n            raise ValueError(\"filter_by must be a non-empty column name.\")\n        if (self.filter_by is None) ^ (self.filter_value is None):\n            raise ValueError(\"filter_by and filter_value must be provided together.\")\n        return self\n\n\nclass DB2VectorSearchTool(BaseTool):\n    \"\"\"\n    Fortified IBM DB2 Vector Search Tool.\n    Includes SQL injection protection, dynamic relational support, and type-safe serialization.\n    \"\"\"\n\n    model_config = ConfigDict(arbitrary_types_allowed=True)\n\n    name: str = \"DB2VectorSearchTool\"\n    description: str = \"Search IBM DB2 vector database for relevant documents. Uses a custom embedding function if supplied, otherwise OpenAI embeddings.\"\n    args_schema: type[BaseModel] = DB2ToolSchema\n\n    # Internal Whitelist for distance metrics to prevent SQL injection\n    # Aligned with Db2 VECTOR_DISTANCE API:\n    # https://www.ibm.com/docs/en/db2/12.1.x?topic=functions-vector-distance","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/db2_search_tool/db2_search_tool.py#L40-L76","documentation":"DB2ToolSchema._validate_pair uses XOR ((filter_by is None) ^ (filter_value is None)) to enforce that the metadata filter is supplied as a complete pair. Providing exactly one of the two — a column without a value, or a value without a column — raises ValueError at Pydantic validation time. Both or neither must be present for the WHERE clause to be well-formed.","triggerScenarios":"tool._run(query='...', filter_by='department') with no filter_value; or filter_value='sales' with no filter_by; agent JSON that includes one field and drops the other; kwargs built by merging dicts where one key gets overwritten.","commonSituations":"LLM tool calls that fill the filter column from the prompt but leave the value blank (or vice versa); refactors where filter_value is accidentally renamed; optional-argument handling that defaults one side to None and the other to ''.","solutions":["Supply both together: filter_by='department', filter_value='sales'.","Or remove both to run an unfiltered vector search.","Add a wrapper assertion: (filter_by is None) == (filter_value is None) before invoking the tool."],"exampleFix":"# before\ntool._run(query='Q3 revenue', filter_by='department')\n\n# after\ntool._run(query='Q3 revenue', filter_by='department', filter_value='finance')","handlingStrategy":"validation","validationCode":"def build_filter_kwargs(filter_by: str | None, filter_value) -> dict:\n    if (filter_by is None) != (filter_value is None):\n        raise ValueError('filter_by and filter_value must be provided together')\n    return {} if filter_by is None else {'filter_by': filter_by, 'filter_value': filter_value}","typeGuard":"def is_complete_filter_pair(kw: dict) -> bool:\n    return (kw.get('filter_by') is None) == (kw.get('filter_value') is None)","tryCatchPattern":"try:\n    tool._run(query=q, **kw)\nexcept ValidationError as e:\n    if 'provided together' in str(e):\n        kw.pop('filter_by', None); kw.pop('filter_value', None)\n        tool._run(query=q, **kw)  # retry unfiltered\n    else:\n        raise","preventionTips":["Construct filter kwargs through one helper that enforces the pair invariant.","Omit keys entirely rather than sending None when no filter is wanted.","Add a unit test asserting XOR inputs raise early in your own code."],"tags":["db2","pydantic","validation","paired-arguments","vector-search"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}