Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
39 changes: 39 additions & 0 deletions openrag/core/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Domain models — pure Pydantic, no infrastructure imports."""

from openrag.core.models.catalog import DocumentRecord, DocumentStatus, IndexationJob, JobStatus
from openrag.core.models.chunk import Chunk, ChunkType
from openrag.core.models.contextualization import ContextualizedQuery
from openrag.core.models.conversation import Conversation, Message
from openrag.core.models.document import Document, DocumentType, ImageBlock, ProcessedDocument, TextBlock
from openrag.core.models.prompt import Prompt, PromptType
from openrag.core.models.query import RetrievalQuery
from openrag.core.models.retrieval_response import RetrievalResponse
from openrag.core.models.retrieval_result import RetrievalResult, ScoredChunk
from openrag.core.models.user import OIDCSession, PartitionRole, User, UserPartition

__all__ = [
"Chunk",
"ChunkType",
"ContextualizedQuery",
"Conversation",
"Document",
"DocumentRecord",
"DocumentStatus",
"DocumentType",
"ImageBlock",
"IndexationJob",
"JobStatus",
"Message",
"OIDCSession",
"PartitionRole",
"ProcessedDocument",
"Prompt",
"PromptType",
"RetrievalQuery",
"RetrievalResponse",
"RetrievalResult",
"ScoredChunk",
"TextBlock",
"User",
"UserPartition",
]
56 changes: 56 additions & 0 deletions openrag/core/models/catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Catalog domain models — document records, indexation jobs, status tracking."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class DocumentStatus(str, Enum):
QUEUED = "QUEUED"
SERIALIZING = "SERIALIZING"
CHUNKING = "CHUNKING"
INSERTING = "INSERTING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
CANCELLED = "CANCELLED"


class JobStatus(str, Enum):
QUEUED = "QUEUED"
RUNNING = "RUNNING"
SUCCESS = "SUCCESS"
FAILED = "FAILED"
PARTIAL = "PARTIAL"


class DocumentRecord(BaseModel):
"""A document entry in the catalog (PostgreSQL)."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
file_id: str = ""
filename: str = ""
partition: str = "default"
metadata: dict[str, Any] = Field(default_factory=dict)
status: DocumentStatus = DocumentStatus.QUEUED
error_message: str | None = None
created_by: int | None = None
relationship_id: str | None = None
parent_id: str | None = None
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))


class IndexationJob(BaseModel):
"""An indexation job tracking batch document processing."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
status: JobStatus = JobStatus.QUEUED
total_documents: int = 0
partition: str = "default"
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
started_at: datetime | None = None
completed_at: datetime | None = None
72 changes: 72 additions & 0 deletions openrag/core/models/chunk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Chunk — the unit of indexable and retrievable text."""

from __future__ import annotations

import uuid
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class ChunkType(str, Enum):
TEXT = "text"
IMAGE_CAPTION = "image_caption"
TABLE = "table"
CONTEXTUALIZED = "contextualized"


class Chunk(BaseModel):
"""A chunk of text extracted from a document, optionally embedded."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
document_id: str = ""
text: str = ""
chunk_index: int = 0
chunk_type: ChunkType = ChunkType.TEXT
embedding: list[float] | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
partition: str = "default"
page_number: int | None = None
token_count: int | None = None
header: str | None = None
context: str | None = None
content: str | None = None

def with_embedding(self, embedding: list[float]) -> Chunk:
"""Return a copy with the embedding set."""
return self.model_copy(update={"embedding": embedding})

@classmethod
def from_langchain(cls, doc: Any) -> Chunk:
"""Convert a LangChain Document to a Chunk.

Import is deferred to method body so core/ stays pure at import time.
"""
metadata = dict(doc.metadata) if doc.metadata else {}
return cls(
id=metadata.pop("_id", str(uuid.uuid4())),
document_id=metadata.pop("file_id", ""),
text=doc.page_content,
partition=metadata.pop("partition", "default"),
page_number=metadata.pop("page", None),
chunk_type=ChunkType(metadata.pop("chunk_type", "text")),
metadata=metadata,
Comment on lines +46 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make chunk_type conversion tolerant of legacy metadata.

Line 53 can raise ValueError for existing LangChain documents whose chunk_type is not exactly one of the new enum values. The existing chunker path in openrag/components/indexer/chunker/chunker.py:245-260 can pass through non-standard entity metadata, so one chunk can abort the whole bridge conversion.

Suggested defensive conversion
         metadata = dict(doc.metadata) if doc.metadata else {}
+        raw_chunk_type = metadata.pop("chunk_type", ChunkType.TEXT.value)
+        try:
+            chunk_type = raw_chunk_type if isinstance(raw_chunk_type, ChunkType) else ChunkType(raw_chunk_type)
+        except ValueError:
+            metadata["chunk_type"] = raw_chunk_type
+            chunk_type = ChunkType.TEXT
+
         return cls(
             id=metadata.pop("_id", str(uuid.uuid4())),
             document_id=metadata.pop("file_id", ""),
             text=doc.page_content,
             partition=metadata.pop("partition", "default"),
             page_number=metadata.pop("page", None),
-            chunk_type=ChunkType(metadata.pop("chunk_type", "text")),
+            chunk_type=chunk_type,
             metadata=metadata,
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openrag/core/models/chunk.py` around lines 46 - 54, The conversion
ChunkType(metadata.pop("chunk_type", "text")) is brittle and can raise
ValueError for legacy/non-standard chunk_type values; change it to pop the raw
value into a variable (e.g., raw_chunk_type), attempt to construct
ChunkType(raw_chunk_type) inside a try/except, and on ValueError map known
legacy strings to the new enum or else fall back to a safe default like
ChunkType.TEXT; ensure the original metadata key is removed (using pop) and that
you reference ChunkType and the class factory (cls(...)/from_doc) so the
conversion is resilient to old metadata formats.

)

def to_langchain(self) -> Any:
"""Convert back to a LangChain Document.

Import is deferred to method body so core/ stays pure at import time.
"""
from langchain_core.documents.base import Document

metadata = {
**self.metadata,
"_id": self.id,
"file_id": self.document_id,
"partition": self.partition,
"page": self.page_number,
"chunk_type": self.chunk_type.value,
}
return Document(page_content=self.text, metadata=metadata)
19 changes: 19 additions & 0 deletions openrag/core/models/contextualization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Contextualized query models — query rewriting, HyDE, reasoning."""

from __future__ import annotations

from pydantic import BaseModel, Field


class ContextualizedQuery(BaseModel):
"""A user query after contextualization by the LLM.

May include rewritten variants, a hypothetical document (HyDE),
and sub-queries for multi-step retrieval.
"""

original: str = ""
query_list: list[str] = Field(default_factory=list)
intent: str = "qa"
hypothetical_doc: str | None = None
sub_queries: list[str] = Field(default_factory=list)
32 changes: 32 additions & 0 deletions openrag/core/models/conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Conversation and message domain models."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from typing import Any

from pydantic import BaseModel, Field


class Message(BaseModel):
"""A single message within a conversation."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
conversation_id: str = ""
role: str = "user"
content: str = ""
sources_json: list[dict[str, Any]] | None = None
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))


class Conversation(BaseModel):
"""A persistent conversation between a user and the RAG system."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: int = 0
partition_scope: str = "default"
title: str = ""
messages: list[Message] = Field(default_factory=list)
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
112 changes: 112 additions & 0 deletions openrag/core/models/document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""Document — the input to the indexing pipeline."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class DocumentType(str, Enum):
PDF = "pdf"
TEXT = "text"
HTML = "html"
MARKDOWN = "markdown"
IMAGE = "image"
AUDIO = "audio"
VIDEO = "video"
DOCX = "docx"
PPTX = "pptx"
DOC = "doc"
EML = "eml"


class TextBlock(BaseModel):
"""A block of text extracted from a document."""

text: str
page_number: int | None = None
block_type: str = "paragraph"
metadata: dict[str, Any] = Field(default_factory=dict)


class ImageBlock(BaseModel):
"""An image extracted from a document."""

image_bytes: bytes = Field(exclude=True, repr=False)
page_number: int | None = None
caption: str | None = None
mime_type: str = "image/png"
metadata: dict[str, Any] = Field(default_factory=dict)


class Document(BaseModel):
"""A document before or during indexing."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
filename: str = ""
content_type: DocumentType = DocumentType.TEXT
text: str | None = None
raw_bytes: bytes | None = Field(None, exclude=True)
partition: str = "default"
tags: list[str] = Field(default_factory=list)
metadata: dict[str, Any] = Field(default_factory=dict)
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

@staticmethod
def detect_content_type(filename: str) -> DocumentType:
"""Detect DocumentType from filename extension."""
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
mapping = {
"pdf": DocumentType.PDF,
"txt": DocumentType.TEXT,
"md": DocumentType.MARKDOWN,
"html": DocumentType.HTML,
"htm": DocumentType.HTML,
"png": DocumentType.IMAGE,
"jpg": DocumentType.IMAGE,
"jpeg": DocumentType.IMAGE,
"mp3": DocumentType.AUDIO,
"wav": DocumentType.AUDIO,
"mp4": DocumentType.VIDEO,
"docx": DocumentType.DOCX,
"pptx": DocumentType.PPTX,
"doc": DocumentType.DOC,
"eml": DocumentType.EML,
}
return mapping.get(ext, DocumentType.TEXT)

@classmethod
def from_langchain(cls, doc: Any) -> Document:
"""Convert a LangChain Document to a domain Document."""
metadata = dict(doc.metadata) if doc.metadata else {}
return cls(
filename=metadata.pop("source", ""),
text=doc.page_content,
partition=metadata.pop("partition", "default"),
metadata=metadata,
)
Comment on lines +82 to +91

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

from_langchain ignores filename-based content type detection.

content_type will always default to DocumentType.TEXT regardless of the source filename (e.g. .pdf, .docx), even though detect_content_type is available right in this class. If callers rely on content_type downstream, LangChain-sourced documents will be mis-tagged.

♻️ Proposed change
     def from_langchain(cls, doc: Any) -> Document:
         """Convert a LangChain Document to a domain Document."""
         metadata = dict(doc.metadata) if doc.metadata else {}
+        filename = metadata.pop("source", "")
         return cls(
-            filename=metadata.pop("source", ""),
+            filename=filename,
+            content_type=cls.detect_content_type(filename),
             text=doc.page_content,
             partition=metadata.pop("partition", "default"),
             metadata=metadata,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@classmethod
def from_langchain(cls, doc: Any) -> Document:
"""Convert a LangChain Document to a domain Document."""
metadata = dict(doc.metadata) if doc.metadata else {}
return cls(
filename=metadata.pop("source", ""),
text=doc.page_content,
partition=metadata.pop("partition", "default"),
metadata=metadata,
)
`@classmethod`
def from_langchain(cls, doc: Any) -> Document:
"""Convert a LangChain Document to a domain Document."""
metadata = dict(doc.metadata) if doc.metadata else {}
filename = metadata.pop("source", "")
return cls(
filename=filename,
content_type=cls.detect_content_type(filename),
text=doc.page_content,
partition=metadata.pop("partition", "default"),
metadata=metadata,
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openrag/core/models/document.py` around lines 82 - 91, The from_langchain
classmethod currently ignores filename-based content detection and always leaves
content_type as the default TEXT; update Document.from_langchain to extract the
source filename (metadata.pop("source", "")) and pass it to the existing
detect_content_type function (or Document.detect_content_type) to set the
document's content_type accordingly before returning the new Document instance;
ensure you preserve other metadata/pop semantics and fallback to
DocumentType.TEXT when detect_content_type returns None or on empty filename.


def to_langchain(self) -> Any:
"""Convert back to a LangChain Document."""
from langchain_core.documents.base import Document as LCDocument

metadata = {
**self.metadata,
"source": self.filename,
"partition": self.partition,
}
return LCDocument(page_content=self.text or "", metadata=metadata)


class ProcessedDocument(BaseModel):
"""Document after parsing/extraction — contains text blocks and images."""

document_id: str = ""
text_blocks: list[TextBlock] = Field(default_factory=list)
images: list[ImageBlock] = Field(default_factory=list)
metadata: dict[str, Any] = Field(default_factory=dict)
page_count: int = 0
31 changes: 31 additions & 0 deletions openrag/core/models/prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Prompt domain models."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from enum import Enum

from pydantic import BaseModel, Field


class PromptType(str, Enum):
SYS_PROMPT = "sys_prompt"
QUERY_CONTEXTUALIZER = "query_contextualizer"
CHUNK_CONTEXTUALIZER = "chunk_contextualizer"
IMAGE_CAPTIONING = "image_captioning"
HYDE = "hyde"
MULTI_QUERY = "multi_query"
SPOKEN_STYLE_ANSWER = "spoken_style_answer"


class Prompt(BaseModel):
"""A prompt template stored in the library."""

id: str = Field(default_factory=lambda: str(uuid.uuid4()))
prompt_type: str = ""
name: str = ""
content: str = ""
is_default: bool = False
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
23 changes: 23 additions & 0 deletions openrag/core/models/query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Retrieval query domain model."""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, Field


class RetrievalQuery(BaseModel):
"""A user query with retrieval parameters."""

text: str
partition: str = "default"
top_k: int = 10
similarity_threshold: float = 0.95
filters: dict[str, Any] = Field(default_factory=dict)
include_related: bool = False
include_ancestors: bool = False
related_limit: int = 10
max_ancestor_depth: int | None = None
Comment on lines +15 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "query.py" | grep -E "openrag.*models.*query"

Repository: linagora/openrag

Length of output: 90


🏁 Script executed:

cat -n openrag/core/models/query.py | head -50

Repository: linagora/openrag

Length of output: 808


🏁 Script executed:

# Check if there are any validators or constraints already in place
rg -A 5 -B 5 "class RetrievalQuery" openrag/

Repository: linagora/openrag

Length of output: 591


🏁 Script executed:

# Check how top_k, related_limit, max_ancestor_depth are used downstream
rg "top_k|related_limit|max_ancestor_depth" openrag/ -A 3 -B 1 --type py | head -100

Repository: linagora/openrag

Length of output: 6853


🏁 Script executed:

# Find all usages of RetrievalQuery class
rg "RetrievalQuery" openrag/ --type py

Repository: linagora/openrag

Length of output: 260


🏁 Script executed:

# Check the vector DB implementation to see if it handles negative/unbounded values
rg "def.*search|def.*get_file_ancestors" openrag/ -A 10 --type py | grep -A 10 "top_k\|related_limit\|max_ancestor_depth" | head -80

Repository: linagora/openrag

Length of output: 5269


🏁 Script executed:

# Look specifically at the vectordb implementation
find openrag -name "*vectordb*" -o -name "*milvus*" | head -5

Repository: linagora/openrag

Length of output: 214


🏁 Script executed:

# Check how RetrievalQuery is actually used - is it instantiated anywhere?
rg "RetrievalQuery(" openrag/ --type py

Repository: linagora/openrag

Length of output: 140


🏁 Script executed:

# Check the actual vectordb implementation and how it uses these parameters
cat -n openrag/components/indexer/vectordb/vectordb.py | head -200

Repository: linagora/openrag

Length of output: 7472


🏁 Script executed:

# Look at the _expand_with_related_chunks implementation which uses related_limit and max_ancestor_depth
rg -B 5 -A 20 "_expand_with_related_chunks" openrag/ --type py

Repository: linagora/openrag

Length of output: 10198


🏁 Script executed:

# Get the full implementation of _expand_with_related_chunks
rg -A 80 "^async def _expand_with_related_chunks" openrag/components/retriever.py

Repository: linagora/openrag

Length of output: 3030


🏁 Script executed:

# Check the get_file_ancestors implementation in vectordb to see if it validates max_ancestor_depth
rg -B 3 -A 20 "async def get_file_ancestors\|def get_file_ancestors" openrag/ --type py

Repository: linagora/openrag

Length of output: 42


🏁 Script executed:

# Look at how top_k is used in the actual Milvus search implementation
rg -B 5 -A 25 "def async_search" openrag/components/indexer/vectordb/vectordb.py | head -150

Repository: linagora/openrag

Length of output: 2103


🏁 Script executed:

# Get more of the async_search implementation to see how top_k and similarity_threshold are used
rg -A 80 "async def async_search" openrag/components/indexer/vectordb/vectordb.py | tail -100

Repository: linagora/openrag

Length of output: 4045


🏁 Script executed:

# Look for where top_k is passed to Milvus search
rg "search_request\|AnnSearchRequest\|top_k" openrag/components/indexer/vectordb/vectordb.py | head -40

Repository: linagora/openrag

Length of output: 42


🏁 Script executed:

# Check if there's validation in get_ancestor_chunks or get_related_chunks for the limits
rg -B 5 -A 20 "async def get_ancestor_chunks\|async def get_related_chunks" openrag/components/indexer/vectordb/vectordb.py | head -100

Repository: linagora/openrag

Length of output: 42


🏁 Script executed:

# Search for any direct instantiation of RetrievalQuery
rg "RetrievalQuery\(" openrag/ --type py -i

Repository: linagora/openrag

Length of output: 121


🏁 Script executed:

# Check if there are any imports of RetrievalQuery elsewhere
rg "from.*RetrievalQuery|import.*RetrievalQuery" openrag/ --type py

Repository: linagora/openrag

Length of output: 144


🏁 Script executed:

# Look at the actual Milvus parameters more carefully - check what happens with negative top_k or out-of-range threshold
cat -n openrag/components/indexer/vectordb/vectordb.py | sed -n '300,450p'

Repository: linagora/openrag

Length of output: 7049


🏁 Script executed:

# Check if RetrievalQuery or other models in openrag/core/models/ use ConfigDict
rg "ConfigDict|model_config" openrag/core/models/ --type py

Repository: linagora/openrag

Length of output: 42


🏁 Script executed:

# Check the complete RetrievalQuery definition to ensure we have the full picture
cat openrag/core/models/query.py

Repository: linagora/openrag

Length of output: 647


🏁 Script executed:

# Verify if RetrievalQuery is actually used anywhere in production code paths
rg "RetrievalQuery" openrag/ --type py -B 2 -A 2

Repository: linagora/openrag

Length of output: 1082


🏁 Script executed:

# Final check: search for any direct instantiation of RetrievalQuery in the codebase
rg "RetrievalQuery\s*\(" openrag/ --type py

Repository: linagora/openrag

Length of output: 121


🏁 Script executed:

# Check if negative/unbounded values would cause issues at the Milvus level
# Look at what happens when top_k is negative or zero
cat -n openrag/components/indexer/vectordb/vectordb.py | sed -n '400,500p'

Repository: linagora/openrag

Length of output: 4653


🏁 Script executed:

# Confirm the complete picture by checking the Milvus search to see what happens with top_k
cat -n openrag/components/indexer/vectordb/vectordb.py | sed -n '540,600p'

Repository: linagora/openrag

Length of output: 3059


Add field validators to constrain caller-controlled retrieval limits.

The domain model RetrievalQuery currently accepts unbounded or negative values for top_k, similarity_threshold, related_limit, and max_ancestor_depth. Although the API layer applies constraints via FastAPI, the exported model itself lacks self-validation. Invalid values could propagate to downstream Milvus calls if the model is instantiated outside the API layer. Apply these Pydantic constraints to ensure the domain model is self-validating:

Suggested Pydantic constraints
-    top_k: int = 10
-    similarity_threshold: float = 0.95
+    top_k: int = Field(default=10, gt=0, le=100)
+    similarity_threshold: float = Field(default=0.95, ge=0.0, le=1.0)
     filters: dict[str, Any] = Field(default_factory=dict)
     include_related: bool = False
     include_ancestors: bool = False
-    related_limit: int = 10
-    max_ancestor_depth: int | None = None
+    related_limit: int = Field(default=10, ge=0, le=100)
+    max_ancestor_depth: int | None = Field(default=None, ge=0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
top_k: int = 10
similarity_threshold: float = 0.95
filters: dict[str, Any] = Field(default_factory=dict)
include_related: bool = False
include_ancestors: bool = False
related_limit: int = 10
max_ancestor_depth: int | None = None
top_k: int = Field(default=10, gt=0, le=100)
similarity_threshold: float = Field(default=0.95, ge=0.0, le=1.0)
filters: dict[str, Any] = Field(default_factory=dict)
include_related: bool = False
include_ancestors: bool = False
related_limit: int = Field(default=10, ge=0, le=100)
max_ancestor_depth: int | None = Field(default=None, ge=0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openrag/core/models/query.py` around lines 15 - 21, Add Pydantic validation
to the RetrievalQuery model to constrain caller-controlled retrieval limits:
enforce top_k >= 1 and reasonable upper bound (e.g., <= 1000),
similarity_threshold between 0.0 and 1.0, related_limit >= 0 and capped (e.g.,
<= 100), and max_ancestor_depth either None or an int >= 0 and capped (e.g., <=
100). Implement these as Field(...) constraints on the RetrievalQuery attributes
(top_k, similarity_threshold, related_limit, max_ancestor_depth) or add
`@validator` methods on class RetrievalQuery to coerce/raise on invalid values and
provide clear error messages referencing the field name.

with_surrounding_chunks: bool = True
rerank: bool = True
Loading
Loading