Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .hydra_config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ reranker:
model_name: ${oc.env:RERANKER_MODEL, Alibaba-NLP/gte-multilingual-reranker-base}
top_k: ${oc.decode:${oc.env:RERANKER_TOP_K, 5}} # Number of documents to return after reranking. upgrade to 8 for better results if your llm has a wider context window
base_url: ${oc.env:RERANKER_BASE_URL, http://reranker:${oc.env:RERANKER_PORT, 7997}}
# Temporal scoring parameters
temporal_weight: ${oc.decode:${oc.env:RERANKER_TEMPORAL_WEIGHT, 0.3}} # Weight for temporal scoring (0.0-1.0), 0.3 means 30% temporal, 70% relevance
temporal_decay_days: ${oc.decode:${oc.env:RERANKER_TEMPORAL_DECAY_DAYS, 365}} # Days for temporal score to decay to near zero

map_reduce:
# Number of documents to process in the initial mapping phase
Expand Down
5 changes: 2 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ include:
- extern/infinity.yaml

x-openrag: &openrag_template
image: ghcr.io/linagora/openrag:dev-latest
# image: linagoraai/openrag:latest
image: linagoraai/openrag:latest
build:
context: .
dockerfile: Dockerfile
Expand Down Expand Up @@ -58,7 +57,7 @@ x-vllm: &vllm_template
services:
# OpenRAG Indexer UI
indexer-ui:
image: linagoraai/indexer-ui:v1.1
image: linagoraai/indexer-ui:latest
build:
context: ./extern/indexer-ui
dockerfile: Dockerfile
Expand Down
82 changes: 81 additions & 1 deletion docs/content/docs/documentation/API.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,45 @@ curl -X POST http://localhost:8080/v1/chat/completions \
}
],
"temperature": 0.7,
"stream": false
"stream": false,
"metadata": {
"use_map_reduce": false,
"temporal_filter": {
"created_after": "2024-01-01T00:00:00Z",
"created_before": "2024-12-31T23:59:59Z"
}
}
}'
```

**Temporal Filtering:**

OpenRAG supports temporal filtering to retrieve documents from specific time periods. You can include a `temporal_filter` object in the `metadata` field:

- **Automatic extraction**: If no `temporal_filter` is provided, OpenRAG automatically extracts temporal expressions from your query (e.g., "documents from 2024", "last week's updates")
- **Manual filtering**: Explicitly specify date ranges using the following fields:
- `datetime_after` / `datetime_before`: Filter by document content datetime (highest priority)
- `modified_after` / `modified_before`: Filter by document modification date
- `created_after` / `created_before`: Filter by document creation date
- `indexed_after` / `indexed_before`: Filter by document indexing date

All dates should be in ISO 8601 format (e.g., `2024-01-15T00:00:00Z`).

**Example with temporal filter:**
```python
# Query documents from 2024 only
response = client.chat.completions.create(
model="openrag-my_partition",
messages=[{"role": "user", "content": "What are the latest updates?"}],
metadata={
"temporal_filter": {
"created_after": "2024-01-01T00:00:00Z",
"created_before": "2024-12-31T23:59:59Z"
}
}
)
```

* Text Completions
```http
POST /v1/completions
Expand Down Expand Up @@ -263,6 +298,51 @@ response = client.chat.completions.create(
)
```

#### Example with Temporal Filtering

```python
from openai import OpenAI
from datetime import datetime, timedelta, timezone

client = OpenAI(api_key='your-auth-token', base_url="http://localhost:8080/v1")

# Example 1: Query recent documents (last 30 days)
thirty_days_ago = (datetime.now(timezone.utc) - timedelta(days=30)).isoformat()
response = client.chat.completions.create(
model="openrag-my_partition",
messages=[{"role": "user", "content": "What are the latest developments?"}],
extra_body={
"metadata": {
"temporal_filter": {
"indexed_after": thirty_days_ago
}
}
}
)

# Example 2: Query documents from a specific year
response = client.chat.completions.create(
model="openrag-my_partition",
messages=[{"role": "user", "content": "What happened in 2024?"}],
extra_body={
"metadata": {
"temporal_filter": {
"created_after": "2024-01-01T00:00:00Z",
"created_before": "2024-12-31T23:59:59Z"
}
}
}
)

# Example 3: Let OpenRAG automatically extract temporal context from query
# Queries like "last week", "documents from 2024", "recent changes"
# will automatically be filtered without explicit temporal_filter
response = client.chat.completions.create(
model="openrag-my_partition",
messages=[{"role": "user", "content": "Show me documents from last week"}]
)
```

---

## ⚠️ Error Handling
Expand Down
42 changes: 42 additions & 0 deletions docs/content/docs/documentation/data_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,45 @@ Defines the many-to-many relationship between **users** and **partitions**, incl

---

## **Vector Database Schema (Milvus)**

In addition to the relational database, OpenRAG uses Milvus to store document chunks with embeddings and metadata.

### Document Chunk Fields

| Field Name | Type | Description |
|------------|------|-------------|
| `embedding` | FloatVector | Dense vector embedding of the chunk content |
| `sparse_vector` | SparseFloatVector | Sparse BM25 vector for hybrid search |
| `content` | VarChar | The actual text content of the chunk |
| `partition` | VarChar | Partition name this chunk belongs to |
| `filename` | VarChar | Source filename |
| `page` | Int64 | Page number in the source document |
| `_id` | VarChar (PK) | Unique chunk identifier |
| `datetime` | VarChar | Primary timestamp from document content (user-provided) |
| `modified_at` | VarChar | Document modification timestamp (ISO 8601 format) |
| `created_at` | VarChar | Document creation timestamp (ISO 8601 format) |
| `indexed_at` | VarChar | When the chunk was indexed into OpenRAG (ISO 8601 format) |

### Temporal Fields Priority

When filtering or scoring documents by time, OpenRAG uses the following priority:
1. **`datetime`** - Highest priority, user-provided timestamp from document content
2. **`modified_at`** - Document modification date
3. **`created_at`** - Document creation date
4. **`indexed_at`** - Fallback, when the document was indexed

All temporal fields are stored in ISO 8601 format (e.g., `2024-01-15T10:30:00Z`).

### Temporal Filtering

Vector database queries support temporal filtering with the following parameters:
- `datetime_after` / `datetime_before`
- `modified_after` / `modified_before`
- `created_after` / `created_before`
- `indexed_after` / `indexed_before`

These filters use **OR logic** between the different date fields to ensure maximum flexibility in retrieving time-relevant documents.

---

45 changes: 45 additions & 0 deletions docs/content/docs/documentation/features_in_details.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,49 @@ See the section on [distributed deployment in a ray cluster](#5-distributed-depl
* **Contextual retrieval** - Anthropic's technique for enhanced chunk relevance
* **Multilingual reranking** - using `Alibaba-NLP/gte-multilingual-reranker-base`

</details>

### 🕐 Temporal Awareness
OpenRAG includes intelligent temporal understanding to deliver more relevant, time-aware responses.

<details>

<summary>Temporal Features</summary>

* **Automatic date extraction** - Detects temporal expressions in queries across multiple languages
* ISO dates: `2024-01-15`, `2024-01-15T10:30:00`
* Numeric formats: `15/01/2024`, `01/15/2024`, `15.01.2024`
* Month-year: `01/2024`, `2024/01`
* Year only: `2024`, `2023`
* Relative time: "last 30 days", "últimos 7 días", "derniers 30 jours"
* Keywords: "today", "yesterday", "recent" (and multilingual equivalents)

* **Document timestamp metadata** - Tracks temporal information for each document
* `datetime` - Primary timestamp from document content (user-provided)
* `modified_at` - Document modification timestamp
* `created_at` - Document creation timestamp
* `indexed_at` - When the document was indexed into OpenRAG

* **Temporal filtering** - Automatically filters search results based on detected time ranges
* Queries like "documents from 2024" only retrieve relevant documents
* "Last week's updates" focuses on recent content
* Works across all retrieval methods (base, multi-query, HyDE)

* **Temporal scoring in reranking** - Balances relevance with recency
* Combines semantic relevance score with temporal score
* Configurable temporal weight (default: 30% temporal, 70% relevance)
* Linear decay formula favors more recent documents
* Configurable decay period (default: 365 days)
* Priority hierarchy: `datetime` > `modified_at` > `created_at` > `indexed_at`

* **Temporal-aware prompts** - LLM receives temporal context
* Current date/time injected into system prompt
* Document timestamps included in retrieved chunks
* LLM instructed to consider recency when answering
* Prioritizes newer information for time-sensitive queries

* **Configuration options** via environment variables:
* `RERANKER_TEMPORAL_WEIGHT` - Weight for temporal scoring (0.0-1.0, default: 0.3)
* `RERANKER_TEMPORAL_DECAY_DAYS` - Days for temporal score decay (default: 365)

</details>
37 changes: 19 additions & 18 deletions openrag/components/indexer/chunker/chunker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional
from datetime import datetime, timezone

from components.prompts import CHUNK_CONTEXTUALIZER
from components.utils import get_llm_semaphore, load_config
Expand Down Expand Up @@ -235,12 +236,12 @@ async def split_document(self, doc: Document, task_id: str = None):
start_page = page_info["start_page"]
end_page = page_info["end_page"]
prev_page_num = end_page
filtered_chunks.append(
Document(
page_content=chunk_w_context,
metadata={**metadata, "page": start_page},
)
)
chunk_meta = {
**metadata,
"page": start_page,
"indexed_at": datetime.now(timezone.utc).isoformat(),
}
filtered_chunks.append(Document(page_content=chunk_w_context, metadata=chunk_meta))
log.info("Document chunking completed")
return filtered_chunks

Expand Down Expand Up @@ -352,12 +353,12 @@ async def split_document(self, doc: Document, task_id: str = None):
start_page = page_info["start_page"]
end_page = page_info["end_page"]
prev_page_num = end_page
filtered_chunks.append(
Document(
page_content=chunk_w_context,
metadata={**metadata, "page": start_page},
)
)
chunk_meta = {
**metadata,
"page": start_page,
"indexed_at": datetime.now(timezone.utc).isoformat(),
}
filtered_chunks.append(Document(page_content=chunk_w_context, metadata=chunk_meta))
log.info("Document chunking completed")
return filtered_chunks

Expand Down Expand Up @@ -457,12 +458,12 @@ async def split_document(self, doc: Document, task_id: str = None):
start_page = page_info["start_page"]
end_page = page_info["end_page"]
prev_page_num = end_page
filtered_chunks.append(
Document(
page_content=chunk_w_context,
metadata={**metadata, "page": start_page},
)
)
chunk_meta = {
**metadata,
"page": start_page,
"indexed_at": datetime.now(timezone.utc).isoformat(),
}
filtered_chunks.append(Document(page_content=chunk_w_context, metadata=chunk_meta))
log.info("Document chunking completed")
return filtered_chunks

Expand Down
Loading
Loading