Skip to main content

Search & Filtering

Learn how to search your vector store effectively and organize documents for better results.


Search Methods Overview

The SDK provides three approaches to search your vector store, each with different use cases:

Use the client's built-in search methods for simple, direct searches:

from caip_agents_sdk import CAIPAgentsClient

client = CAIPAgentsClient()

# Generate embedding for your query
query_embedding = await client.text_to_embedding(
text="What are authentication methods?",
vector_store_id="your-vector-store-id"
)

# Vector search (semantic similarity)
search_results = await client.vector_search(
vector_store_id="your-vector-store-id",
search_request={
"queryEmbedding": query_embedding,
"limit": 10,
"scoreThreshold": 0.0,
"includeEmbeddings": False
}
)

# Extract results from response (Pydantic model)
results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

# Hybrid search (semantic + keyword)
query_embedding = await client.text_to_embedding(
text="authentication methods",
vector_store_id="your-vector-store-id"
)

search_results = await client.hybrid_search(
vector_store_id="your-vector-store-id",
search_request={
"queryText": "authentication methods",
"queryEmbedding": query_embedding,
"limit": 5,
"vectorWeight": 0.7,
"scoreThreshold": 0.0,
"includeEmbeddings": False
}
)

results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

# Keyword search (exact text matching)
search_results = await client.keyword_search(
vector_store_id="your-vector-store-id",
search_request={
"queryText": "Bearer token",
"limit": 5,
"fuzzy": True
}
)

results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

# Keyword search with metadata filter
search_request = {
"queryText": "authentication",
"limit": 5,
"fuzzy": True
}

# Optional: add metadata filter
metadata_filter = {"category": "api", "version": "v2"}
if metadata_filter:
search_request["metadataFilter"] = metadata_filter

# Perform keyword search with filter
search_results = await client.keyword_search(
vector_store_id="your-vector-store-id",
search_request=search_request
)

results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

Advantages:

  • ✅ Direct control over search parameters
  • ✅ Simple for one-off searches
  • ✅ Good for scripts and data pipelines
  • ✅ No agent setup needed

Limitations:

  • ❌ Requires manual embedding generation for vector search
  • ❌ No automatic result formatting for agents
  • ❌ Need to handle errors yourself
  • ❌ Not integrated with agent tools

Use this when: Building scripts, data pipelines, or need direct API access without agents.

The KnowledgeBaseSearchTool is a production-ready agent tool that handles all the complexity:

from caip_agents_sdk.tools import KnowledgeBaseSearchTool

# Create the prebuilt search tool
kb_tool = KnowledgeBaseSearchTool(
client=client,
vector_store_id="vs_abc123"
)

# Add to your agent
@agent.tool_plain
async def search_docs(query: str) -> str:
"""Search product documentation."""
return await kb_tool.search(
query=query,
search_type="hybrid", # or "vector" or "keyword"
top_k=5
)

Advantages:

  • ✅ Automatic embedding generation
  • ✅ Multiple search strategies (hybrid, vector, keyword)
  • ✅ Built-in result formatting for agents
  • ✅ Filtering support (document ID, metadata)
  • ✅ Error handling and retries
  • ✅ Production-tested and optimized
  • ✅ Agent-friendly responses

Why better than direct methods for agents:

  • 🎯 Agent-optimized: Results formatted for LLM consumption
  • 🎯 Simplified API: No manual embedding generation
  • 🎯 Multiple strategies: Switch between hybrid/vector/keyword easily
  • 🎯 Reliable: Built-in error handling and edge cases covered

Use this when: Building agents with RAG capabilities (recommended for most cases).

3. Custom Search Tool (Advanced)

Build your own tool for specialized requirements

Advantages:

  • ✅ Full control over search logic
  • ✅ Custom result formatting
  • ✅ Specialized pre/post processing
  • ✅ Integration with other tools/services

Use this when: You need custom logic, specialized formatting, or unique search behavior.

Learn more: See Custom RAG Search Tool in the Tools documentation.

Quick Decision:

  • Building an agent? → Use Prebuilt Tool ⭐
  • Writing a script? → Use Direct Client Methods
  • Need custom logic? → Build Custom Tool

Search Strategies

All three methods support three search strategies. Choose the right one for your query:

Combines semantic meaning + keyword matching - best for most cases:

Direct Client:

# Generate embedding first
query_embedding = await client.text_to_embedding(
text="authentication methods",
vector_store_id="your-vector-store-id"
)

# Perform hybrid search
search_results = await client.hybrid_search(
vector_store_id="your-vector-store-id",
search_request={
"queryText": "authentication methods",
"queryEmbedding": query_embedding,
"limit": 5,
"vectorWeight": 0.7, # 0.7 = 70% vector, 30% keyword
"scoreThreshold": 0.0,
"includeEmbeddings": False
}
)

# Extract results
results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

Prebuilt Tool:

@agent.tool_plain
async def search_docs(query: str) -> str:
"""Search documentation."""
return await kb_tool.search(
query=query,
search_type="hybrid", # Recommended
top_k=5
)

✅ Use for: General queries, best default choice


Search by semantic meaning (finds concepts, not exact words):

Direct Client:

# Generate embedding first
query_embedding = await client.text_to_embedding(
text="What are authentication methods?",
vector_store_id="your-vector-store-id"
)

# Perform vector search
search_results = await client.vector_search(
vector_store_id="your-vector-store-id",
search_request={
"queryEmbedding": query_embedding,
"limit": 5,
"scoreThreshold": 0.0,
"includeEmbeddings": False
}
)

# Extract results
results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

Prebuilt Tool:

@agent.tool_plain
async def semantic_search(query: str) -> str:
"""Search by meaning/concept."""
return await kb_tool.search(
query=query,
search_type="vector", # Auto-generates embeddings
top_k=5
)

✅ Use for: Conceptual questions like "What are authentication methods?" (finds related concepts)


Search for exact words or phrases:

Direct Client:

# Perform keyword search
search_results = await client.keyword_search(
vector_store_id="your-vector-store-id",
search_request={
"queryText": "Bearer token",
"limit": 5,
"fuzzy": True
}
)

# Extract results
results = search_results.results if hasattr(search_results, 'results') else []
print(f"✅ Found {len(results)} results")

Prebuilt Tool:

@agent.tool_plain
async def keyword_search(query: str) -> str:
"""Search for exact words."""
return await kb_tool.search(
query=query,
search_type="keyword",
top_k=5
)

✅ Use for: Exact terms like "Bearer token", technical identifiers, error codes


Which Strategy to Use?

Your NeedUse This
General questionshybrid ⭐ (best default)
Conceptual questionsvector (finds meaning)
Exact terms/phraseskeyword (exact match)
Not sure?hybrid

Key insight: The prebuilt tool automatically generates embeddings for vector and hybrid search, while direct client methods require you to do it manually.


Document IDs: Organization

Why Document IDs Matter

Good document IDs help you:

  • ✅ Filter searches to specific sections
  • ✅ Update or delete specific documents
  • ✅ Track and manage content
  • ✅ Make searches faster and more accurate

Good vs Bad IDs

❌ Bad - Generic IDs:

documentIds = ["doc_1", "doc_2", "doc_3"] # No context!

✅ Good - Structured IDs:

documentIds = [
"api/authentication", # Clear hierarchy
"api/rate-limits",
"guides/getting-started"
]

ID Patterns

Use forward slashes for structure:

documentIds = [
"api/v2/authentication",
"api/v2/rate-limits",
"guides/quickstart",
"guides/advanced"
]

Benefits: Easy filtering, clear organization

2. Filename-Based

Use actual file paths:

documentIds = [
"docs/user-guide.pdf",
"docs/api-reference.md",
"kb/faq.html"
]

Benefits: Traceability, easy to update

3. Source-Based

Track content origin:

documentIds = [
"confluence:PROJ-123:page-title",
"github:repo/docs/README.md",
"sharepoint:sales/Q1-2024"
]

Benefits: Multi-source tracking, access control


Filtering Search Results

Make searches faster and more accurate by filtering.

Filter by Document ID

Search within specific sections:

@agent.tool_plain
async def search_api_docs(query: str) -> str:
"""Search only API documentation."""
return await kb_tool.search(
query=query,
search_type="hybrid",
document_id="api/", # Only "api/*" documents
top_k=5
)

Why use this?

  • ✅ Faster: Searches fewer documents
  • ✅ More accurate: Results from specific section only
  • ✅ Supports prefixes: "api/" matches all API docs

Filter by Metadata

Filter by categories, versions, or custom fields:

@agent.tool_plain
async def search_tutorials(query: str) -> str:
"""Search only tutorials."""
return await kb_tool.search(
query=query,
search_type="hybrid",
metadata_filter={"category": "tutorials"},
top_k=5
)

Combine Filters

Use both for maximum precision:

@agent.tool_plain
async def search_v2_auth(query: str) -> str:
"""Search only v2 authentication docs."""
return await kb_tool.search(
query=query,
search_type="hybrid",
document_id="api/authentication/",
metadata_filter={"version": "v2"},
top_k=5
)

When to Use Filters

Your NeedUse This
Search specific sectiondocument_id filter
Filter by category/versionmetadata_filter
Maximum precisionBoth combined
Explore everythingNo filters

Metadata Best Practices

Good Metadata

Rich, structured metadata improves search:

# ✅ Good - Rich metadata
metadata = {
"category": "api",
"subcategory": "authentication",
"version": "v2",
"page": 15,
"last_updated": "2024-03-01"
}

Bad Metadata

# ❌ Bad - Minimal metadata
metadata = {"type": "doc"} # Not useful!

Metadata Examples

# Documentation
metadata = {
"category": "docs",
"topic": "authentication",
"difficulty": "beginner"
}

# API Reference
metadata = {
"category": "api",
"endpoint": "/auth/login",
"method": "POST",
"version": "v2"
}

# Support Articles
metadata = {
"category": "support",
"issue_type": "login_problems",
"popularity": "high"
}

Complete Search Example

from caip_agents_sdk import CAIPAgentsClient
from caip_agents_sdk.tools import KnowledgeBaseSearchTool

async def setup_search_agent():
"""Agent with multiple search tools."""
client = CAIPAgentsClient()

# Create agent
agent = client.create_agent("pydantic_ai", "your-agent-id")
await agent.initialize()

# Create search tool
kb_tool = KnowledgeBaseSearchTool(
client=client,
vector_store_id="your_vector_store_id"
)

# 1. General search (hybrid)
@agent.tool_plain
async def search_all(query: str) -> str:
"""Search all documentation."""
return await kb_tool.search(
query=query,
search_type="hybrid",
top_k=5
)

# 2. API docs only
@agent.tool_plain
async def search_api(query: str) -> str:
"""Search API documentation only."""
return await kb_tool.search(
query=query,
search_type="hybrid",
metadata_filter={"category": "api"},
top_k=5
)

# 3. Tutorials only
@agent.tool_plain
async def search_tutorials(query: str) -> str:
"""Search tutorials only."""
return await kb_tool.search(
query=query,
search_type="hybrid",
metadata_filter={"category": "tutorials"},
top_k=5
)

# Use the agent
response = await agent.run("How do I authenticate?")
print(response)

if __name__ == "__main__":
import asyncio
asyncio.run(setup_search_agent())

Next Steps