Python Indexing and Search
This guide continues from Conversation History and shows how to add search indexing to history entries and expose a search API for frontend applications.
New to indexing and search concepts? Read Indexing & Search first to understand
indexedContent, redaction, and search types.
Prerequisites
Starting checkpoint: This guide starts from python/examples/langchain/doc-checkpoints/03-with-history
Make sure you’ve completed the Python Conversation History guide first.
Also complete Step 2 in Python Dev Setup (build local memory-service-langchain wheel + UV_FIND_LINKS); this is temporary until the package is released.
How Search Indexing Works
Conversation history content is encrypted at rest, so text search needs a separate cleartext index field.
Checkpoint 07 configures MemoryServiceHistoryMiddleware with an indexed_content_provider. The provider transforms each history message into indexedContent before the entry is written.
This gives you a redaction point. You can remove sensitive data from indexedContent while still storing the full encrypted message content.
Add an Indexed Content Provider
The simplest provider is pass-through:
def pass_through_indexed_content(text: str, role: str) -> str:
del role
return text
history_middleware = MemoryServiceHistoryMiddleware.from_env(
indexed_content_provider=pass_through_indexed_content,
) What changed: pass_through_indexed_content(text, role) is defined and passed to MemoryServiceHistoryMiddleware.from_env(indexed_content_provider=...). Without an indexed_content_provider, history entries are written with no indexedContent field and therefore cannot be searched.
Why: The indexed_content_provider hook is called before each entry is written, and whatever string it returns is stored as the cleartext indexedContent field that the search index reads. Returning the message text verbatim (pass-through) makes every message fully searchable. The del role line suppresses an unused-argument linting warning; in production code the role argument would be used to skip indexing AI responses or to apply different redaction rules per speaker.
Security warning
indexedContentis not encrypted. Redact or minimize sensitive values before returning indexed text.
For production, replace pass-through with redaction logic. Example:
def redacting_indexed_content(text: str, role: str) -> str:
del role
return re.sub(r"\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b", "[REDACTED]", text)
Expose the Search API
Expose POST /v1/conversations/search so frontend apps can query across conversations:
return to_fastapi_response(response) Why: The search query, search type (auto, semantic, fulltext, or a concrete type array like ["semantic","fulltext"]), and pagination options all live in the JSON request body, so the endpoint requires no query-parameter parsing — it just reads the body and passes it through. The Memory Service returns scored results with highlights, which the proxy relays back to the caller. Exposing the endpoint from the agent app rather than pointing the frontend directly at the Memory Service keeps all traffic behind a single origin and ensures the user’s Bearer token is validated in one place.
Make sure you define a shell function that can get the bearer token for the bob user:
function get-token() {
curl -sSfX POST http://localhost:8081/realms/memory-service/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=memory-service-client" \
-d "client_secret=change-me" \
-d "grant_type=password" \
-d "username=bob" \
-d "password=bob" \
| jq -r '.access_token'
}First, create searchable conversation content:
curl -NsSfX POST http://localhost:9090/chat/09637cfd-aaa6-4828-860c-34fa9081c060 \
-H "Authorization: Bearer $(get-token)" \
-H "Content-Type: text/plain" \
-d "Give me a random number between 1 and 100." Example output:
Sure, I can help with that. Search conversations:
curl -sSfX POST http://localhost:9090/v1/conversations/search \
-H "Authorization: Bearer $(get-token)" \
-H "Content-Type: application/json" \
-d '{"query": "random number", "searchType": "auto"}' | jq Example output:
{
"data": [
{
"conversationId": "09637cfd-aaa6-4828-860c-34fa9081c060"
}
],
"afterCursor": null
} Completed Checkpoint
Completed code: View the full implementation at python/examples/langchain/doc-checkpoints/07-with-search
Next Steps
Continue to:
- Conversation Forking - Branch conversations to explore alternative paths
- Response Recording and Resumption - Streaming responses with resume and cancel support