When using the AgentOS API, you can apply filters to precisely control which knowledge base documents your agents search, without changing your agent code.
Filter expressions serialize to JSON and are automatically reconstructed server-side for powerful, programmatic filtering.
Two Approaches to Filtering
Agno supports two ways to filter knowledge through the API:
Use dictionary filters for simple “field = value” lookups
Use filter expressions when you need OR/NOT logic or ranges
1. Dictionary Filters (Simple)
Best for straightforward equality matching. Send a JSON object with key-value pairs:
{ "docs" : "agno" , "status" : "published" }
2. Filter Expressions (Advanced)
Best for complex filtering with full logical control. Send structured filter objects:
{ "op" : "AND" , "conditions" : [
{ "op" : "EQ" , "key" : "docs" , "value" : "agno" },
{ "op" : "GT" , "key" : "version" , "value" : 2 }
]}
When to use which:
Use dict filters for simple queries like filtering by category or status
Use filter expressions when you need OR logic, exclusions (NOT), range queries (GT/LT/GTE/LTE), inequality (NEQ), or substring/prefix matching (CONTAINS/STARTSWITH)
Filter Operators
Filter expressions support comparison, string-matching, and logical operators.
Comparison Operators
EQ(key, value) - Equality: field equals value
NEQ(key, value) - Inequality: field does not equal value
GT(key, value) - Greater than: field > value
GTE(key, value) - Greater than or equal: field >= value
LT(key, value) - Less than: field < value
LTE(key, value) - Less than or equal: field <= value
IN(key, [values]) - Inclusion: field in list of values
String Matching Operators
CONTAINS(key, value) - field contains the substring (case-insensitive)
STARTSWITH(key, value) - field starts with the given prefix
Logical Operators
AND(*filters) - All conditions must be true
OR(*filters) - At least one condition must be true
NOT(filter) - Negate a condition
Nesting limit: Filter expressions can be nested up to 10 levels deep. Deeper expressions are rejected during deserialization, fall into the error-handling path, and the request proceeds without filters (with a warning logged).
Python operator overloads
Filter expressions support Python’s bitwise operators as shorthand for AND, OR, and NOT:
from agno.filters import EQ , GT
# These two are equivalent
EQ( "status" , "published" ) & GT( "views" , 1000 )
AND(EQ( "status" , "published" ), GT( "views" , 1000 ))
# OR
EQ( "priority" , "high" ) | EQ( "urgent" , True )
# NOT
~ EQ( "status" , "draft" )
Filter expression objects use a dictionary format with an "op" key that distinguishes them from regular dict filters. Field names differ per operator: IN takes values (plural); NOT takes condition (singular); AND/OR take conditions (plural); everything else takes value.
Comparison and string operators
All comparison operators (EQ, NEQ, GT, GTE, LT, LTE) and string operators (CONTAINS, STARTSWITH) share the same shape:
{ "op" : "EQ" , "key" : "status" , "value" : "published" }
{ "op" : "GTE" , "key" : "views" , "value" : 1000 }
{ "op" : "CONTAINS" , "key" : "title" , "value" : "agno" }
IN takes values (plural)
{ "op" : "IN" , "key" : "category" , "values" : [ "tech" , "science" ]}
Passing "value" instead of "values" is rejected by the deserializer.
AND and OR take conditions (plural)
{
"op" : "AND" ,
"conditions" : [
{ "op" : "EQ" , "key" : "status" , "value" : "published" },
{ "op" : "GT" , "key" : "views" , "value" : 1000 }
]
}
NOT takes condition (singular)
{
"op" : "NOT" ,
"condition" : { "op" : "EQ" , "key" : "status" , "value" : "archived" }
}
Round-trip example
from agno.filters import EQ , GT , AND
filter_expr = AND(EQ( "status" , "published" ), GT( "views" , 1000 ))
filter_expr.to_dict()
# {
# "op": "AND",
# "conditions": [
# {"op": "EQ", "key": "status", "value": "published"},
# {"op": "GT", "key": "views", "value": 1000}
# ]
# }
The presence of the "op" key tells the API to deserialize the filter as a filter expression. Regular dict filters (without "op") continue to work for backward compatibility.
Using Filters Through the API
In all examples below, you pass the serialized JSON string via the knowledge_filters field when creating a run.
Dictionary Filters (Simple Approach)
For basic filtering, send a JSON object with key-value pairs. All conditions are combined with AND logic:
import requests
import json
# Simple dict filter
filter_dict = { "docs" : "agno" , "status" : "published" }
# Serialize to JSON
filter_json = json.dumps(filter_dict)
# Send request
response = requests.post(
"http://localhost:7777/agents/agno-knowledge-agent/runs" ,
data = {
"message" : "What are agno's key features?" ,
"stream" : "false" ,
"knowledge_filters" : filter_json,
}
)
result = response.json()
More Dict Filter Examples:
# Filter by single field
{ "category" : "technology" }
# Filter by multiple fields (AND logic)
{ "category" : "technology" , "status" : "published" , "year" : 2024 }
# Filter with different data types
{ "active" : True , "priority" : 1 , "department" : "engineering" }
Filter Expressions (Advanced Approach)
For complex filtering with logical operators and comparisons:
import requests
import json
from agno.filters import EQ
# Create filter expression
filter_expr = EQ( "category" , "technology" )
# Serialize to JSON
filter_json = json.dumps(filter_expr.to_dict())
# Send request
response = requests.post(
"http://localhost:7777/agents/my-agent/runs" ,
data = {
"message" : "What are the latest tech articles?" ,
"stream" : "false" ,
"knowledge_filters" : filter_json,
}
)
result = response.json()
Multiple Filter Expressions
Send multiple filter expressions as a JSON array:
from agno.filters import EQ , GT
# Create multiple filters
filters = [
EQ( "status" , "published" ),
GT( "date" , "2024-01-01" )
]
# Serialize list to JSON
filters_json = json.dumps([f.to_dict() for f in filters])
response = requests.post(
"http://localhost:7777/agents/my-agent/runs" ,
data = {
"message" : "Show recent published articles" ,
"stream" : "false" ,
"knowledge_filters" : filters_json,
}
)
Error Handling
Invalid Filter Structure
When filters have errors, they’re gracefully ignored with warnings:
# Missing required fields
curl ... -F 'knowledge_filters={"op": "EQ", "key": "status"}'
# Result: Filter ignored, warning logged
# Unknown operator
curl ... -F 'knowledge_filters={"op": "UNKNOWN", "key": "status", "value": "x"}'
# Result: Filter ignored, warning logged
# Invalid JSON
curl ... -F 'knowledge_filters={invalid json}'
# Result: Filter ignored, warning logged
# Filter nested deeper than 10 levels
curl ... -F 'knowledge_filters={"op": "NOT", "condition": {"op": "NOT", "condition": ...}}'
# Result: Filter ignored, warning logged (depth limit exceeded)
When filters fail to parse, the search proceeds without filters rather than throwing an error. Always verify your filter JSON is valid and check server logs if results seem unfiltered.
Client-Side Validation
Add validation before sending requests:
def validate_and_send_filter ( filter_expr , message ):
"""Validate filter before sending to API."""
try :
# Test serialization
filter_dict = filter_expr.to_dict()
filter_json = json.dumps(filter_dict)
# Verify it's valid JSON
json.loads(filter_json)
# Send request
return send_filtered_agent_request(message, filter_expr)
except ( AttributeError , TypeError , json.JSONDecodeError) as e:
print ( f "Filter validation failed: { e } " )
return None
Next Steps
Advanced Filtering Guide Learn about filter expressions and metadata design in detail
Agent OS API Explore the full Agent OS API reference
Knowledge Bases Understand knowledge base architecture and setup
Search & Retrieval Optimize your search strategies
Filter Expressions Example See working examples of filter expressions via API