Skip to content

Commit 5208b11

Browse files
committed
docs: Add Contains() and SearchQueryFilterClause subsections to ADR-0065
- Add 'Text Search Contains() Support Implementation' subsection - Documents query enhancement pattern for Brave/Tavily - Explains SearchQueryFilterClause design decision - Lists alternatives and consequences - Add 'SearchQueryFilterClause Public Visibility' subsection - Documents public API decision and rationale - Explains FilterClause internal constructor constraint - Lists alternatives considered (move to Plugins.Web, InternalsVisibleTo, etc.) These implementation decisions are integral to the overall LINQ-based text search filtering architecture and belong within the main ADR. #10456
1 parent c971790 commit 5208b11

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed

dotnet/docs/decisions/0065-linq-based-text-search-filtering.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,90 @@ public interface ITextSearch
386386

387387
**Estimated Timeline**: Phase 2 in next major version (e.g., SK 2.0), Phase 3 in subsequent major version (e.g., SK 3.0). This gives ecosystem minimum 1-2 years to migrate.
388388

389+
### Text Search Contains() Support Implementation
390+
391+
The `ITextSearch<TRecord>` interface supports LINQ expressions, including `Title.Contains("value")` patterns. Different search engine APIs have varying capabilities for handling text matching within specific fields:
392+
393+
- **Bing**: Native advanced search operators (`intitle:`, `inbody:`, `url:`)
394+
- **Google**: Specialized API parameters (`orTerms` for additional search terms)
395+
- **Brave/Tavily**: General search APIs without field-specific operators
396+
397+
**Decision**: Implement `Title.Contains()` support using **query enhancement** for Brave and Tavily search engines:
398+
399+
1. **SearchQueryFilterClause**: New filter clause type that adds terms to the search query rather than filtering results
400+
2. **Query Enhancement Pattern**: Extract terms from `SearchQueryFilterClause` instances and append to base search query
401+
3. **Dual Processing**: Handle `SearchQueryFilterClause` differently from regular filter clauses in `ConvertToLegacyOptionsWithQuery`
402+
403+
**Implementation Pattern**:
404+
405+
```csharp
406+
// LINQ Expression: results.Where(r => r.Title.Contains("AI"))
407+
// Converts to: new SearchQueryFilterClause("AI")
408+
// Query Enhancement: "original query" + " AI"
409+
```
410+
411+
**Alternatives Considered**:
412+
413+
1. **Direct API Parameters**: Not available in Brave/Tavily APIs
414+
2. **Post-Search Filtering**: Would reduce result relevance and performance
415+
3. **NotSupportedException**: Would limit LINQ expression capabilities
416+
417+
**Consequences**:
418+
419+
- ✅ Consistent LINQ expression support across search engines
420+
- ✅ Enhanced search relevance by modifying query rather than filtering results
421+
- ✅ Extensible pattern for future Contains() implementations
422+
- ✅ Maintains backward compatibility with legacy `ITextSearch` interface
423+
- ⚠️ Different implementation approaches across search engines (consistency concern)
424+
- ⚠️ Additional complexity in filter clause processing
425+
- ⚠️ Query enhancement may affect search precision in some cases
426+
427+
### SearchQueryFilterClause Public Visibility
428+
429+
**Context**: Reviewer feedback requested moving `SearchQueryFilterClause` from `VectorData.Abstractions` to `Plugins.Web` to reduce public API surface. Investigation revealed architectural constraint preventing this move.
430+
431+
**Problem**: `FilterClause` base class has an **internal constructor**, preventing inheritance outside the `VectorData.Abstractions` assembly:
432+
433+
```csharp
434+
public abstract class FilterClause
435+
{
436+
internal FilterClause() // ← Blocks external inheritance
437+
}
438+
```
439+
440+
Attempted move to `Plugins.Web` fails with:
441+
```
442+
error CS0122: 'FilterClause.FilterClause()' is inaccessible due to its protection level
443+
```
444+
445+
**Decision**: Make `SearchQueryFilterClause` **public** and keep it in `VectorData.Abstractions`.
446+
447+
```csharp
448+
public sealed class SearchQueryFilterClause : FilterClause
449+
```
450+
451+
**Rationale**:
452+
453+
- **Why public**: Respects existing `FilterClause` architecture (internal constructor is intentional design)
454+
- Legitimate reusable abstraction for text search scenarios
455+
- Consistent with precedent: `EqualToFilterClause`, `AnyTagEqualToFilterClause` are public
456+
- Cleanest solution with no workarounds
457+
458+
**Alternatives Considered**:
459+
460+
1. **Move to Plugins.Web**: Impossible due to internal constructor
461+
2. **Internal + InternalsVisibleTo**: Causes 200 CS0436 type conflict errors in CI
462+
3. **Make FilterClause constructor public**: Too broad, opens VectorData to external filter types
463+
4. **Don't inherit from FilterClause**: Breaks established pattern, loses type safety
464+
465+
**Consequences**:
466+
467+
- ✅ Respects intentional `FilterClause` architecture
468+
- ✅ Maintains consistency with other public filter clause types
469+
- ✅ Clean implementation with no workarounds
470+
- ✅ Reusable for future text search scenarios
471+
- ⚠️ Adds to public API surface (minimal impact - single sealed class)
472+
389473
## Pros and Cons of the Options
390474

391475
### Option 1: Native LINQ Only

0 commit comments

Comments
 (0)