Add recency bias to semantic search (timestamps in prompts + time-decay reranking) - #930
Add recency bias to semantic search (timestamps in prompts + time-decay reranking)#930crspeller wants to merge 5 commits into
Conversation
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
|
Cursor Agent can help with this pull request. Just |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 944452c9e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🤖 LLM Evaluation ResultsOpenAI
❌ Failed EvaluationsShow 7 failuresOPENAI1. TestReactEval/[openai]_react_cat_message
2. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
3. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
4. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
5. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
6. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
7. TestDirectMessageConversations/[openai]_bot_dm_tool_introspection
Anthropic
❌ Failed EvaluationsShow 7 failuresANTHROPIC1. TestReactEval/[anthropic]_react_cat_message
2. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
3. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
4. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
5. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
6. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
7. TestDirectMessageConversations/[anthropic]_bot_dm_tool_introspection
This comment was automatically generated by the eval CI pipeline. |
📝 WalkthroughWalkthroughSemantic search now supports configurable recency-biased reranking. Creation timestamps flow through search APIs and MCP results into formatted prompts, while the embedding search administration panel exposes recency settings. ChangesSemantic search recency and timestamps
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant SearchConfig
participant CompositeSearch
participant VectorStore
participant PromptBuilder
Admin->>SearchConfig: Configure recency settings
SearchConfig->>CompositeSearch: Pass resolved settings
CompositeSearch->>VectorStore: Fetch expanded candidates
VectorStore-->>CompositeSearch: Return timestamped results
CompositeSearch-->>PromptBuilder: Return reranked results
PromptBuilder-->>Admin: Render optional message times
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@embeddings/recency.go`:
- Around line 90-101: Update recencyFetchLimit to cap the computed
offset-plus-pool value at the search limit enforced by postgres.PGVector.Search,
using a shared maximum-search constant or contract if available. Preserve the
existing zero-limit behavior and candidate-pool calculations while ensuring the
returned fetch limit never exceeds the store’s hard cap.
In `@prompts/search_results.tmpl`:
- Line 1: Update the time attribute condition in the Results range to evaluate
the formatted timestamp returned by formatTime rather than the raw .CreateAt
value, so negative timestamps omit the attribute when formatting yields an empty
string. Preserve the existing formatTime output and attribute rendering for
valid timestamps.
In `@prompts/search_system.tmpl`:
- Line 11: Update the message timestamp guidance near the recency rule to state
that timestamps are optional and should only be used when present. Preserve the
instruction to prefer newer messages when timestamps are available, and avoid
implying recency for messages without timestamps.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: e30a6016-df75-4a16-9b62-69169f4616b0
📒 Files selected for processing (23)
api/api_search.goembeddings/composite.goembeddings/composite_test.goembeddings/embeddings.goembeddings/integration_test.goembeddings/recency.goembeddings/recency_test.goformat/format.goformat/format_test.gollm/prompts.gomcpserver/eval_helpers_test.gomcpserver/tools/search.gomcpserver/tools/search_http.gomcpserver/tools/search_http_test.goprompts/search_results.tmplprompts/search_system.tmplsearch/embeddings.gosearch/search.gosearch/search_eval_test.gosearch/search_test.gowebapp/src/components/system_console/embedding_search/embedding_search_panel.tsxwebapp/src/components/system_console/embedding_search/types.tsxwebapp/src/i18n/en.json
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…tional Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Summary
Semantic search currently ranks results purely by vector similarity, so stale posts can outrank fresh ones and the answering LLM never sees when a message was written. This PR adds a recency bias in two layers:
1. Expose post timestamps to the LLM (always on)
search.RAGResultnow carriesCreateAt;prompts/search_results.tmplrenders it as atime="RFC3339"attribute (omitted when the timestamp is unknown) andsearch_system.tmplinstructs the model to prefer recent messages when results conflict, without inferring recency for undated messages.search_poststool output and the/api/v1/search/rawendpoint (plus the external MCP server's HTTP client) now include the post timestamp too.format.TimeFromMillishelper shared byformat.WritePostand the prompt template funcformatTime.2. Opt-in time-decay reranking (over-fetch + rerank)
When enabled,
CompositeSearch.Searchover-fetches candidates by pure ANN similarity (4× limit, bounded to [20, 200], offset folded in), reranks them in memory with a floored exponential half-life decay, then applies the caller's offset/limit window to the reranked order:adjusted = rawScore × (floor + (1 − floor) × 0.5^(ageDays / halfLifeDays))Defaults: 7-day half-life, 0.7 floor — modeled on Elasticsearch
function_scoreexp-decay and Azure AI Search freshness boosting. The floor bounds worst-case demotion so an old canonical answer (raw 0.9 → 0.63) still outranks a weak fresh match (< 0.63).The returned
Scorestays the raw similarity:MinScorefiltering and the relevance surfaced to callers remain similarity-based; recency influences ordering only. Ties break by newerCreateAt.The pure
ORDER BY embedding <-> $1inner query is unchanged, so the HNSW index stays fully usable.Deep pagination keeps working: the store's per-search cap is now a shared contract (
embeddings.MaxSearchResults, used by pgvector), and when a requested window extends past the rerankable candidate pool the search falls back to store-side pagination in raw similarity order instead of silently truncating deep pages.New config fields on
EmbeddingSearchConfig(recencyBiasEnabled,recencyHalfLifeDays,recencyFloor) with defaulting/clamping viaGetRecencyBiasSettings(), mirrored in the system console Embedding Search panel. These are query-time-only settings; changing them never requires a reindex.Tests
embeddings/recency_test.go: decay multiplier math, candidate pool sizing, config resolution, and rerank ordering (fresh overtakes decayed, floor guarantee, unknown-timestamp handling, recency tie-break, raw score preservation).embeddings/composite_test.go: end-to-end rerank throughCompositeSearch.Search— over-fetch options, reranked order, offset/limit applied post-rerank, disabled passthrough, and store-pagination fallback for windows beyond the search cap.embeddings/integration_test.go(TestRecencyBiasEndToEnd): full path against real pgvector — two posts with identical content (identical similarity via the deterministic mock provider) where the newer post must rank first, proving the ordering comes from the recency rerank.search/search_test.go:CreateAtenrichment andtime="…"rendering (including omission for missing/negative timestamps) through the real prompt templates.format,mcpserver/toolstests for the new timestamp plumbing.Verified locally:
go vet,golangci-lint(0 issues), mattermost-govet license check, webapp tsc/eslint/jest, i18n and e2e-shard drift checks, and the Go test suite including thepostgrespgvector suite against a real container.Ticket Link
NONE
Screenshots
N/A (three new fields in the existing Embedding Search system console panel)
Release Note
Summary by CodeRabbit
New Features
Improvements
Bug Fixes