filters and settings + ci fixes
This commit is contained in:
parent
956674e0ae
commit
648074d3c8
15 changed files with 1054 additions and 2 deletions
6
.github/workflows/test-integration.yml
vendored
6
.github/workflows/test-integration.yml
vendored
|
|
@ -7,6 +7,7 @@ on:
|
||||||
- 'tests/**.py'
|
- 'tests/**.py'
|
||||||
- 'pyproject.toml'
|
- 'pyproject.toml'
|
||||||
- 'uv.lock'
|
- 'uv.lock'
|
||||||
|
- 'sdks/**'
|
||||||
- '.github/workflows/test-integration.yml'
|
- '.github/workflows/test-integration.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
|
|
@ -58,6 +59,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
- name: Python version
|
- name: Python version
|
||||||
run: uv python install 3.13
|
run: uv python install 3.13
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ from .exceptions import (
|
||||||
ServerError,
|
ServerError,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
|
from .knowledge_filters import KnowledgeFiltersClient
|
||||||
from .models import (
|
from .models import (
|
||||||
AgentSettings,
|
AgentSettings,
|
||||||
ChatResponse,
|
ChatResponse,
|
||||||
|
|
@ -43,18 +44,28 @@ from .models import (
|
||||||
Conversation,
|
Conversation,
|
||||||
ConversationDetail,
|
ConversationDetail,
|
||||||
ConversationListResponse,
|
ConversationListResponse,
|
||||||
|
CreateKnowledgeFilterOptions,
|
||||||
|
CreateKnowledgeFilterResponse,
|
||||||
DeleteDocumentResponse,
|
DeleteDocumentResponse,
|
||||||
|
DeleteKnowledgeFilterResponse,
|
||||||
DoneEvent,
|
DoneEvent,
|
||||||
|
GetKnowledgeFilterResponse,
|
||||||
IngestResponse,
|
IngestResponse,
|
||||||
|
KnowledgeFilter,
|
||||||
|
KnowledgeFilterQueryData,
|
||||||
|
KnowledgeFilterSearchResponse,
|
||||||
KnowledgeSettings,
|
KnowledgeSettings,
|
||||||
Message,
|
Message,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
SearchResult,
|
SearchResult,
|
||||||
SettingsResponse,
|
SettingsResponse,
|
||||||
|
SettingsUpdateOptions,
|
||||||
|
SettingsUpdateResponse,
|
||||||
Source,
|
Source,
|
||||||
SourcesEvent,
|
SourcesEvent,
|
||||||
StreamEvent,
|
StreamEvent,
|
||||||
|
UpdateKnowledgeFilterOptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
|
|
@ -62,6 +73,8 @@ __version__ = "0.1.0"
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Main client
|
# Main client
|
||||||
"OpenRAGClient",
|
"OpenRAGClient",
|
||||||
|
# Sub-clients
|
||||||
|
"KnowledgeFiltersClient",
|
||||||
# Exceptions
|
# Exceptions
|
||||||
"OpenRAGError",
|
"OpenRAGError",
|
||||||
"AuthenticationError",
|
"AuthenticationError",
|
||||||
|
|
@ -86,6 +99,17 @@ __all__ = [
|
||||||
"ConversationListResponse",
|
"ConversationListResponse",
|
||||||
"Message",
|
"Message",
|
||||||
"SettingsResponse",
|
"SettingsResponse",
|
||||||
|
"SettingsUpdateOptions",
|
||||||
|
"SettingsUpdateResponse",
|
||||||
"AgentSettings",
|
"AgentSettings",
|
||||||
"KnowledgeSettings",
|
"KnowledgeSettings",
|
||||||
|
# Knowledge filter models
|
||||||
|
"KnowledgeFilter",
|
||||||
|
"KnowledgeFilterQueryData",
|
||||||
|
"CreateKnowledgeFilterOptions",
|
||||||
|
"UpdateKnowledgeFilterOptions",
|
||||||
|
"CreateKnowledgeFilterResponse",
|
||||||
|
"KnowledgeFilterSearchResponse",
|
||||||
|
"GetKnowledgeFilterResponse",
|
||||||
|
"DeleteKnowledgeFilterResponse",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ class ChatStream:
|
||||||
filters: SearchFilters | dict[str, Any] | None = None,
|
filters: SearchFilters | dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
score_threshold: float = 0,
|
score_threshold: float = 0,
|
||||||
|
filter_id: str | None = None,
|
||||||
):
|
):
|
||||||
self._client = client
|
self._client = client
|
||||||
self._message = message
|
self._message = message
|
||||||
|
|
@ -65,6 +66,7 @@ class ChatStream:
|
||||||
self._filters = filters
|
self._filters = filters
|
||||||
self._limit = limit
|
self._limit = limit
|
||||||
self._score_threshold = score_threshold
|
self._score_threshold = score_threshold
|
||||||
|
self._filter_id = filter_id
|
||||||
|
|
||||||
# Aggregated data
|
# Aggregated data
|
||||||
self._text = ""
|
self._text = ""
|
||||||
|
|
@ -105,6 +107,9 @@ class ChatStream:
|
||||||
else:
|
else:
|
||||||
body["filters"] = self._filters
|
body["filters"] = self._filters
|
||||||
|
|
||||||
|
if self._filter_id:
|
||||||
|
body["filter_id"] = self._filter_id
|
||||||
|
|
||||||
self._response = await self._client._http.send(
|
self._response = await self._client._http.send(
|
||||||
self._client._http.build_request(
|
self._client._http.build_request(
|
||||||
"POST",
|
"POST",
|
||||||
|
|
@ -214,6 +219,7 @@ class ChatClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None = None,
|
filters: SearchFilters | dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
score_threshold: float = 0,
|
score_threshold: float = 0,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> ChatResponse: ...
|
) -> ChatResponse: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
|
@ -226,6 +232,7 @@ class ChatClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None = None,
|
filters: SearchFilters | dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
score_threshold: float = 0,
|
score_threshold: float = 0,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> AsyncIterator[StreamEvent]: ...
|
) -> AsyncIterator[StreamEvent]: ...
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
|
|
@ -237,6 +244,7 @@ class ChatClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None = None,
|
filters: SearchFilters | dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
score_threshold: float = 0,
|
score_threshold: float = 0,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> ChatResponse | AsyncIterator[StreamEvent]:
|
) -> ChatResponse | AsyncIterator[StreamEvent]:
|
||||||
"""
|
"""
|
||||||
Send a chat message.
|
Send a chat message.
|
||||||
|
|
@ -248,6 +256,7 @@ class ChatClient:
|
||||||
filters: Optional search filters (data_sources, document_types).
|
filters: Optional search filters (data_sources, document_types).
|
||||||
limit: Maximum number of search results (default 10).
|
limit: Maximum number of search results (default 10).
|
||||||
score_threshold: Minimum search score threshold (default 0).
|
score_threshold: Minimum search score threshold (default 0).
|
||||||
|
filter_id: Optional knowledge filter ID to apply.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ChatResponse if stream=False, AsyncIterator[StreamEvent] if stream=True.
|
ChatResponse if stream=False, AsyncIterator[StreamEvent] if stream=True.
|
||||||
|
|
@ -269,6 +278,7 @@ class ChatClient:
|
||||||
filters=filters,
|
filters=filters,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
score_threshold=score_threshold,
|
score_threshold=score_threshold,
|
||||||
|
filter_id=filter_id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return await self._create_response(
|
return await self._create_response(
|
||||||
|
|
@ -277,6 +287,7 @@ class ChatClient:
|
||||||
filters=filters,
|
filters=filters,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
score_threshold=score_threshold,
|
score_threshold=score_threshold,
|
||||||
|
filter_id=filter_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _create_response(
|
async def _create_response(
|
||||||
|
|
@ -286,6 +297,7 @@ class ChatClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None,
|
filters: SearchFilters | dict[str, Any] | None,
|
||||||
limit: int,
|
limit: int,
|
||||||
score_threshold: float,
|
score_threshold: float,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> ChatResponse:
|
) -> ChatResponse:
|
||||||
"""Send a non-streaming chat message."""
|
"""Send a non-streaming chat message."""
|
||||||
body: dict[str, Any] = {
|
body: dict[str, Any] = {
|
||||||
|
|
@ -304,6 +316,9 @@ class ChatClient:
|
||||||
else:
|
else:
|
||||||
body["filters"] = filters
|
body["filters"] = filters
|
||||||
|
|
||||||
|
if filter_id:
|
||||||
|
body["filter_id"] = filter_id
|
||||||
|
|
||||||
response = await self._client._request(
|
response = await self._client._request(
|
||||||
"POST",
|
"POST",
|
||||||
"/api/v1/chat",
|
"/api/v1/chat",
|
||||||
|
|
@ -326,6 +341,7 @@ class ChatClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None,
|
filters: SearchFilters | dict[str, Any] | None,
|
||||||
limit: int,
|
limit: int,
|
||||||
score_threshold: float,
|
score_threshold: float,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> AsyncIterator[StreamEvent]:
|
) -> AsyncIterator[StreamEvent]:
|
||||||
"""Stream a chat response as an async iterator."""
|
"""Stream a chat response as an async iterator."""
|
||||||
body: dict[str, Any] = {
|
body: dict[str, Any] = {
|
||||||
|
|
@ -344,6 +360,9 @@ class ChatClient:
|
||||||
else:
|
else:
|
||||||
body["filters"] = filters
|
body["filters"] = filters
|
||||||
|
|
||||||
|
if filter_id:
|
||||||
|
body["filter_id"] = filter_id
|
||||||
|
|
||||||
async with self._client._http.stream(
|
async with self._client._http.stream(
|
||||||
"POST",
|
"POST",
|
||||||
f"{self._client._base_url}/api/v1/chat",
|
f"{self._client._base_url}/api/v1/chat",
|
||||||
|
|
@ -387,6 +406,7 @@ class ChatClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None = None,
|
filters: SearchFilters | dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
score_threshold: float = 0,
|
score_threshold: float = 0,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> ChatStream:
|
) -> ChatStream:
|
||||||
"""
|
"""
|
||||||
Create a streaming chat context manager.
|
Create a streaming chat context manager.
|
||||||
|
|
@ -397,6 +417,7 @@ class ChatClient:
|
||||||
filters: Optional search filters (data_sources, document_types).
|
filters: Optional search filters (data_sources, document_types).
|
||||||
limit: Maximum number of search results (default 10).
|
limit: Maximum number of search results (default 10).
|
||||||
score_threshold: Minimum search score threshold (default 0).
|
score_threshold: Minimum search score threshold (default 0).
|
||||||
|
filter_id: Optional knowledge filter ID to apply.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ChatStream context manager.
|
ChatStream context manager.
|
||||||
|
|
@ -418,6 +439,7 @@ class ChatClient:
|
||||||
filters=filters,
|
filters=filters,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
score_threshold=score_threshold,
|
score_threshold=score_threshold,
|
||||||
|
filter_id=filter_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def list(self) -> ConversationListResponse:
|
async def list(self) -> ConversationListResponse:
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from .exceptions import (
|
||||||
ServerError,
|
ServerError,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
|
from .knowledge_filters import KnowledgeFiltersClient
|
||||||
from .search import SearchClient
|
from .search import SearchClient
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ class SettingsClient:
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
"""
|
"""
|
||||||
Get current OpenRAG configuration (read-only).
|
Get current OpenRAG configuration.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SettingsResponse with agent and knowledge settings.
|
SettingsResponse with agent and knowledge settings.
|
||||||
|
|
@ -37,6 +38,27 @@ class SettingsClient:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return SettingsResponse(**data)
|
return SettingsResponse(**data)
|
||||||
|
|
||||||
|
async def update(self, options):
|
||||||
|
"""
|
||||||
|
Update OpenRAG configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
options: SettingsUpdateOptions or dict with settings to update.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SettingsUpdateResponse with success message.
|
||||||
|
"""
|
||||||
|
from .models import SettingsUpdateOptions, SettingsUpdateResponse
|
||||||
|
|
||||||
|
if isinstance(options, SettingsUpdateOptions):
|
||||||
|
body = options.model_dump(exclude_none=True)
|
||||||
|
else:
|
||||||
|
body = {k: v for k, v in options.items() if v is not None}
|
||||||
|
|
||||||
|
response = await self._client._request("POST", "/settings", json=body)
|
||||||
|
data = response.json()
|
||||||
|
return SettingsUpdateResponse(message=data.get("message", "Settings updated"))
|
||||||
|
|
||||||
|
|
||||||
class OpenRAGClient:
|
class OpenRAGClient:
|
||||||
"""
|
"""
|
||||||
|
|
@ -110,6 +132,7 @@ class OpenRAGClient:
|
||||||
self.search = SearchClient(self)
|
self.search = SearchClient(self)
|
||||||
self.documents = DocumentsClient(self)
|
self.documents = DocumentsClient(self)
|
||||||
self.settings = SettingsClient(self)
|
self.settings = SettingsClient(self)
|
||||||
|
self.knowledge_filters = KnowledgeFiltersClient(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _headers(self) -> dict[str, str]:
|
def _headers(self) -> dict[str, str]:
|
||||||
|
|
|
||||||
230
sdks/python/openrag_sdk/knowledge_filters.py
Normal file
230
sdks/python/openrag_sdk/knowledge_filters.py
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
"""OpenRAG SDK knowledge filters client."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
CreateKnowledgeFilterOptions,
|
||||||
|
CreateKnowledgeFilterResponse,
|
||||||
|
DeleteKnowledgeFilterResponse,
|
||||||
|
GetKnowledgeFilterResponse,
|
||||||
|
KnowledgeFilter,
|
||||||
|
KnowledgeFilterQueryData,
|
||||||
|
KnowledgeFilterSearchResponse,
|
||||||
|
UpdateKnowledgeFilterOptions,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .client import OpenRAGClient
|
||||||
|
|
||||||
|
|
||||||
|
class KnowledgeFiltersClient:
|
||||||
|
"""Client for knowledge filter operations."""
|
||||||
|
|
||||||
|
def __init__(self, client: "OpenRAGClient"):
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
async def create(
|
||||||
|
self,
|
||||||
|
options: CreateKnowledgeFilterOptions | dict[str, Any],
|
||||||
|
) -> CreateKnowledgeFilterResponse:
|
||||||
|
"""
|
||||||
|
Create a new knowledge filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
options: The filter options including name and query_data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created filter response with ID.
|
||||||
|
"""
|
||||||
|
if isinstance(options, CreateKnowledgeFilterOptions):
|
||||||
|
name = options.name
|
||||||
|
description = options.description or ""
|
||||||
|
query_data = options.query_data
|
||||||
|
else:
|
||||||
|
name = options["name"]
|
||||||
|
description = options.get("description", "")
|
||||||
|
query_data = options.get("query_data") or options.get("queryData", {})
|
||||||
|
|
||||||
|
# Convert query_data to JSON string if it's a model
|
||||||
|
if isinstance(query_data, KnowledgeFilterQueryData):
|
||||||
|
query_data_str = query_data.model_dump_json(by_alias=True, exclude_none=True)
|
||||||
|
elif isinstance(query_data, dict):
|
||||||
|
query_data_str = json.dumps(query_data)
|
||||||
|
else:
|
||||||
|
query_data_str = str(query_data)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
"queryData": query_data_str,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await self._client._request(
|
||||||
|
"POST",
|
||||||
|
"/knowledge-filter",
|
||||||
|
json=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return CreateKnowledgeFilterResponse(
|
||||||
|
success=data.get("success", False),
|
||||||
|
id=data.get("id"),
|
||||||
|
error=data.get("error"),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def search(
|
||||||
|
self,
|
||||||
|
query: str = "",
|
||||||
|
limit: int = 20,
|
||||||
|
) -> list[KnowledgeFilter]:
|
||||||
|
"""
|
||||||
|
Search for knowledge filters by name, description, or query content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: Optional search query text.
|
||||||
|
limit: Maximum number of results (default 20).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of matching knowledge filters.
|
||||||
|
"""
|
||||||
|
body = {
|
||||||
|
"query": query,
|
||||||
|
"limit": limit,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await self._client._request(
|
||||||
|
"POST",
|
||||||
|
"/knowledge-filter/search",
|
||||||
|
json=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success") or not data.get("filters"):
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [self._parse_filter(f) for f in data["filters"]]
|
||||||
|
|
||||||
|
async def get(self, filter_id: str) -> KnowledgeFilter | None:
|
||||||
|
"""
|
||||||
|
Get a specific knowledge filter by ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_id: The ID of the filter to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The knowledge filter or None if not found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = await self._client._request(
|
||||||
|
"GET",
|
||||||
|
f"/knowledge-filter/{filter_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success") or not data.get("filter"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._parse_filter(data["filter"])
|
||||||
|
except Exception:
|
||||||
|
# Filter not found or other error
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
self,
|
||||||
|
filter_id: str,
|
||||||
|
options: UpdateKnowledgeFilterOptions | dict[str, Any],
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Update an existing knowledge filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_id: The ID of the filter to update.
|
||||||
|
options: The fields to update.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success status.
|
||||||
|
"""
|
||||||
|
body: dict[str, Any] = {}
|
||||||
|
|
||||||
|
if isinstance(options, UpdateKnowledgeFilterOptions):
|
||||||
|
if options.name is not None:
|
||||||
|
body["name"] = options.name
|
||||||
|
if options.description is not None:
|
||||||
|
body["description"] = options.description
|
||||||
|
if options.query_data is not None:
|
||||||
|
body["queryData"] = options.query_data.model_dump_json(
|
||||||
|
by_alias=True, exclude_none=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if "name" in options:
|
||||||
|
body["name"] = options["name"]
|
||||||
|
if "description" in options:
|
||||||
|
body["description"] = options["description"]
|
||||||
|
query_data = options.get("query_data") or options.get("queryData")
|
||||||
|
if query_data is not None:
|
||||||
|
if isinstance(query_data, KnowledgeFilterQueryData):
|
||||||
|
body["queryData"] = query_data.model_dump_json(
|
||||||
|
by_alias=True, exclude_none=True
|
||||||
|
)
|
||||||
|
elif isinstance(query_data, dict):
|
||||||
|
body["queryData"] = json.dumps(query_data)
|
||||||
|
else:
|
||||||
|
body["queryData"] = str(query_data)
|
||||||
|
|
||||||
|
response = await self._client._request(
|
||||||
|
"PUT",
|
||||||
|
f"/knowledge-filter/{filter_id}",
|
||||||
|
json=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return data.get("success", False)
|
||||||
|
|
||||||
|
async def delete(self, filter_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Delete a knowledge filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_id: The ID of the filter to delete.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success status.
|
||||||
|
"""
|
||||||
|
response = await self._client._request(
|
||||||
|
"DELETE",
|
||||||
|
f"/knowledge-filter/{filter_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
return data.get("success", False)
|
||||||
|
|
||||||
|
def _parse_filter(self, data: dict[str, Any]) -> KnowledgeFilter:
|
||||||
|
"""Parse a filter from API response, handling JSON-stringified query_data."""
|
||||||
|
query_data = data.get("query_data") or data.get("queryData")
|
||||||
|
if isinstance(query_data, str):
|
||||||
|
try:
|
||||||
|
query_data = json.loads(query_data)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
query_data = {}
|
||||||
|
|
||||||
|
parsed_query_data = None
|
||||||
|
if query_data:
|
||||||
|
parsed_query_data = KnowledgeFilterQueryData(
|
||||||
|
query=query_data.get("query"),
|
||||||
|
filters=query_data.get("filters"),
|
||||||
|
limit=query_data.get("limit"),
|
||||||
|
score_threshold=query_data.get("scoreThreshold"),
|
||||||
|
color=query_data.get("color"),
|
||||||
|
icon=query_data.get("icon"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return KnowledgeFilter(
|
||||||
|
id=data["id"],
|
||||||
|
name=data["name"],
|
||||||
|
description=data.get("description"),
|
||||||
|
query_data=parsed_query_data,
|
||||||
|
owner=data.get("owner"),
|
||||||
|
created_at=data.get("created_at") or data.get("createdAt"),
|
||||||
|
updated_at=data.get("updated_at") or data.get("updatedAt"),
|
||||||
|
)
|
||||||
|
|
@ -158,3 +158,103 @@ class SearchFilters(BaseModel):
|
||||||
|
|
||||||
data_sources: list[str] | None = None
|
data_sources: list[str] | None = None
|
||||||
document_types: list[str] | None = None
|
document_types: list[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# Settings update models
|
||||||
|
class SettingsUpdateOptions(BaseModel):
|
||||||
|
"""Options for updating settings."""
|
||||||
|
|
||||||
|
llm_model: str | None = None
|
||||||
|
llm_provider: str | None = None
|
||||||
|
system_prompt: str | None = None
|
||||||
|
embedding_model: str | None = None
|
||||||
|
embedding_provider: str | None = None
|
||||||
|
chunk_size: int | None = None
|
||||||
|
chunk_overlap: int | None = None
|
||||||
|
table_structure: bool | None = None
|
||||||
|
ocr: bool | None = None
|
||||||
|
picture_descriptions: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsUpdateResponse(BaseModel):
|
||||||
|
"""Response from settings update."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
# Knowledge filter models
|
||||||
|
class KnowledgeFilterQueryData(BaseModel):
|
||||||
|
"""Query configuration stored in a knowledge filter."""
|
||||||
|
|
||||||
|
query: str | None = None
|
||||||
|
filters: dict[str, list[str]] | None = None
|
||||||
|
limit: int | None = None
|
||||||
|
score_threshold: float | None = Field(default=None, alias="scoreThreshold")
|
||||||
|
color: str | None = None
|
||||||
|
icon: str | None = None
|
||||||
|
|
||||||
|
model_config = {"populate_by_name": True}
|
||||||
|
|
||||||
|
|
||||||
|
class KnowledgeFilter(BaseModel):
|
||||||
|
"""A knowledge filter definition."""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
description: str | None = None
|
||||||
|
query_data: KnowledgeFilterQueryData | None = Field(default=None, alias="queryData")
|
||||||
|
owner: str | None = None
|
||||||
|
created_at: str | None = Field(default=None, alias="createdAt")
|
||||||
|
updated_at: str | None = Field(default=None, alias="updatedAt")
|
||||||
|
|
||||||
|
model_config = {"populate_by_name": True}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateKnowledgeFilterOptions(BaseModel):
|
||||||
|
"""Options for creating a knowledge filter."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
description: str | None = None
|
||||||
|
query_data: KnowledgeFilterQueryData = Field(alias="queryData")
|
||||||
|
|
||||||
|
model_config = {"populate_by_name": True}
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateKnowledgeFilterOptions(BaseModel):
|
||||||
|
"""Options for updating a knowledge filter."""
|
||||||
|
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
query_data: KnowledgeFilterQueryData | None = Field(default=None, alias="queryData")
|
||||||
|
|
||||||
|
model_config = {"populate_by_name": True}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateKnowledgeFilterResponse(BaseModel):
|
||||||
|
"""Response from creating a knowledge filter."""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
id: str | None = None
|
||||||
|
error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class KnowledgeFilterSearchResponse(BaseModel):
|
||||||
|
"""Response from searching knowledge filters."""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
filters: list[KnowledgeFilter] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class GetKnowledgeFilterResponse(BaseModel):
|
||||||
|
"""Response from getting a knowledge filter."""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
filter: KnowledgeFilter | None = None
|
||||||
|
error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteKnowledgeFilterResponse(BaseModel):
|
||||||
|
"""Response from deleting a knowledge filter."""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
error: str | None = None
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class SearchClient:
|
||||||
filters: SearchFilters | dict[str, Any] | None = None,
|
filters: SearchFilters | dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
score_threshold: float = 0,
|
score_threshold: float = 0,
|
||||||
|
filter_id: str | None = None,
|
||||||
) -> SearchResponse:
|
) -> SearchResponse:
|
||||||
"""
|
"""
|
||||||
Perform semantic search on documents.
|
Perform semantic search on documents.
|
||||||
|
|
@ -32,6 +33,7 @@ class SearchClient:
|
||||||
filters: Optional filters (data_sources, document_types).
|
filters: Optional filters (data_sources, document_types).
|
||||||
limit: Maximum number of results (default 10).
|
limit: Maximum number of results (default 10).
|
||||||
score_threshold: Minimum score threshold (default 0).
|
score_threshold: Minimum score threshold (default 0).
|
||||||
|
filter_id: Optional knowledge filter ID to apply.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SearchResponse containing the search results.
|
SearchResponse containing the search results.
|
||||||
|
|
@ -48,6 +50,9 @@ class SearchClient:
|
||||||
else:
|
else:
|
||||||
body["filters"] = filters
|
body["filters"] = filters
|
||||||
|
|
||||||
|
if filter_id:
|
||||||
|
body["filter_id"] = filter_id
|
||||||
|
|
||||||
response = await self._client._request(
|
response = await self._client._request(
|
||||||
"POST",
|
"POST",
|
||||||
"/api/v1/search",
|
"/api/v1/search",
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,124 @@ class TestSettings:
|
||||||
assert settings.agent is not None
|
assert settings.agent is not None
|
||||||
assert settings.knowledge is not None
|
assert settings.knowledge is not None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_settings(self, client):
|
||||||
|
"""Test updating settings."""
|
||||||
|
# Get current settings first
|
||||||
|
current_settings = await client.settings.get()
|
||||||
|
current_chunk_size = current_settings.knowledge.chunk_size or 1000
|
||||||
|
|
||||||
|
# Update with the same value (safe for tests)
|
||||||
|
result = await client.settings.update({"chunk_size": current_chunk_size})
|
||||||
|
|
||||||
|
assert result.message is not None
|
||||||
|
|
||||||
|
# Verify the setting persisted
|
||||||
|
updated_settings = await client.settings.get()
|
||||||
|
assert updated_settings.knowledge.chunk_size == current_chunk_size
|
||||||
|
|
||||||
|
|
||||||
|
class TestKnowledgeFilters:
|
||||||
|
"""Test knowledge filter operations."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_knowledge_filter_crud(self, client):
|
||||||
|
"""Test create, read, update, delete for knowledge filters."""
|
||||||
|
# Create
|
||||||
|
create_result = await client.knowledge_filters.create({
|
||||||
|
"name": "Python SDK Test Filter",
|
||||||
|
"description": "Filter created by Python SDK integration tests",
|
||||||
|
"queryData": {
|
||||||
|
"query": "test documents",
|
||||||
|
"limit": 10,
|
||||||
|
"scoreThreshold": 0.5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert create_result.success is True
|
||||||
|
assert create_result.id is not None
|
||||||
|
filter_id = create_result.id
|
||||||
|
|
||||||
|
# Search
|
||||||
|
filters = await client.knowledge_filters.search("Python SDK Test")
|
||||||
|
assert isinstance(filters, list)
|
||||||
|
found = any(f.name == "Python SDK Test Filter" for f in filters)
|
||||||
|
assert found is True
|
||||||
|
|
||||||
|
# Get
|
||||||
|
filter_obj = await client.knowledge_filters.get(filter_id)
|
||||||
|
assert filter_obj is not None
|
||||||
|
assert filter_obj.id == filter_id
|
||||||
|
assert filter_obj.name == "Python SDK Test Filter"
|
||||||
|
|
||||||
|
# Update
|
||||||
|
update_success = await client.knowledge_filters.update(
|
||||||
|
filter_id,
|
||||||
|
{"description": "Updated description from Python SDK test"},
|
||||||
|
)
|
||||||
|
assert update_success is True
|
||||||
|
|
||||||
|
# Verify update
|
||||||
|
updated_filter = await client.knowledge_filters.get(filter_id)
|
||||||
|
assert updated_filter.description == "Updated description from Python SDK test"
|
||||||
|
|
||||||
|
# Delete
|
||||||
|
delete_success = await client.knowledge_filters.delete(filter_id)
|
||||||
|
assert delete_success is True
|
||||||
|
|
||||||
|
# Verify deletion
|
||||||
|
deleted_filter = await client.knowledge_filters.get(filter_id)
|
||||||
|
assert deleted_filter is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_filter_id_in_chat(self, client):
|
||||||
|
"""Test using filter_id in chat."""
|
||||||
|
# Create a filter first
|
||||||
|
create_result = await client.knowledge_filters.create({
|
||||||
|
"name": "Chat Test Filter Python",
|
||||||
|
"description": "Filter for testing chat with filter_id",
|
||||||
|
"queryData": {
|
||||||
|
"query": "test",
|
||||||
|
"limit": 5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert create_result.success is True
|
||||||
|
filter_id = create_result.id
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use filter in chat
|
||||||
|
response = await client.chat.create(
|
||||||
|
message="Hello with filter",
|
||||||
|
filter_id=filter_id,
|
||||||
|
)
|
||||||
|
assert response.response is not None
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
await client.knowledge_filters.delete(filter_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_filter_id_in_search(self, client):
|
||||||
|
"""Test using filter_id in search."""
|
||||||
|
# Create a filter first
|
||||||
|
create_result = await client.knowledge_filters.create({
|
||||||
|
"name": "Search Test Filter Python",
|
||||||
|
"description": "Filter for testing search with filter_id",
|
||||||
|
"queryData": {
|
||||||
|
"query": "test",
|
||||||
|
"limit": 5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert create_result.success is True
|
||||||
|
filter_id = create_result.id
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use filter in search
|
||||||
|
results = await client.search.query("test query", filter_id=filter_id)
|
||||||
|
assert results.results is not None
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
await client.knowledge_filters.delete(filter_id)
|
||||||
|
|
||||||
|
|
||||||
class TestDocuments:
|
class TestDocuments:
|
||||||
"""Test document operations."""
|
"""Test document operations."""
|
||||||
|
|
@ -222,3 +340,30 @@ class TestChat:
|
||||||
|
|
||||||
assert result.conversations is not None
|
assert result.conversations is not None
|
||||||
assert isinstance(result.conversations, list)
|
assert isinstance(result.conversations, list)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_conversation(self, client):
|
||||||
|
"""Test getting a specific conversation."""
|
||||||
|
# Create a conversation first
|
||||||
|
response = await client.chat.create(message="Test message for get.")
|
||||||
|
assert response.chat_id is not None
|
||||||
|
|
||||||
|
# Get the conversation
|
||||||
|
conversation = await client.chat.get(response.chat_id)
|
||||||
|
|
||||||
|
assert conversation.chat_id == response.chat_id
|
||||||
|
assert conversation.messages is not None
|
||||||
|
assert isinstance(conversation.messages, list)
|
||||||
|
assert len(conversation.messages) >= 1
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_conversation(self, client):
|
||||||
|
"""Test deleting a conversation."""
|
||||||
|
# Create a conversation first
|
||||||
|
response = await client.chat.create(message="Test message for delete.")
|
||||||
|
assert response.chat_id is not None
|
||||||
|
|
||||||
|
# Delete the conversation
|
||||||
|
result = await client.chat.delete(response.chat_id)
|
||||||
|
|
||||||
|
assert result is True
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,10 @@ export class ChatStream implements AsyncIterable<StreamEvent>, Disposable {
|
||||||
body["filters"] = this.options.filters;
|
body["filters"] = this.options.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.options.filterId) {
|
||||||
|
body["filter_id"] = this.options.filterId;
|
||||||
|
}
|
||||||
|
|
||||||
this._response = await this.client._request("POST", "/api/v1/chat", {
|
this._response = await this.client._request("POST", "/api/v1/chat", {
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
stream: true,
|
stream: true,
|
||||||
|
|
@ -218,6 +222,10 @@ export class ChatClient {
|
||||||
body["filters"] = options.filters;
|
body["filters"] = options.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.filterId) {
|
||||||
|
body["filter_id"] = options.filterId;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await this.client._request("POST", "/api/v1/chat", {
|
const response = await this.client._request("POST", "/api/v1/chat", {
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import { ChatClient } from "./chat";
|
import { ChatClient } from "./chat";
|
||||||
import { DocumentsClient } from "./documents";
|
import { DocumentsClient } from "./documents";
|
||||||
import { SearchClient } from "./search";
|
import { SearchClient } from "./search";
|
||||||
|
import { KnowledgeFiltersClient } from "./knowledge-filters";
|
||||||
import {
|
import {
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
|
@ -13,6 +14,8 @@ import {
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
ServerError,
|
ServerError,
|
||||||
SettingsResponse,
|
SettingsResponse,
|
||||||
|
SettingsUpdateOptions,
|
||||||
|
SettingsUpdateResponse,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
|
|
@ -31,7 +34,7 @@ class SettingsClient {
|
||||||
constructor(private client: OpenRAGClient) {}
|
constructor(private client: OpenRAGClient) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current OpenRAG configuration (read-only).
|
* Get current OpenRAG configuration.
|
||||||
*/
|
*/
|
||||||
async get(): Promise<SettingsResponse> {
|
async get(): Promise<SettingsResponse> {
|
||||||
const response = await this.client._request("GET", "/api/v1/settings");
|
const response = await this.client._request("GET", "/api/v1/settings");
|
||||||
|
|
@ -41,6 +44,22 @@ class SettingsClient {
|
||||||
knowledge: data.knowledge || {},
|
knowledge: data.knowledge || {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update OpenRAG configuration.
|
||||||
|
*
|
||||||
|
* @param options - The settings to update.
|
||||||
|
* @returns Success response with message.
|
||||||
|
*/
|
||||||
|
async update(options: SettingsUpdateOptions): Promise<SettingsUpdateResponse> {
|
||||||
|
const response = await this.client._request("POST", "/settings", {
|
||||||
|
body: JSON.stringify(options),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
message: data.message || "Settings updated",
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RequestOptions {
|
interface RequestOptions {
|
||||||
|
|
@ -84,6 +103,8 @@ export class OpenRAGClient {
|
||||||
readonly documents: DocumentsClient;
|
readonly documents: DocumentsClient;
|
||||||
/** Settings client for configuration. */
|
/** Settings client for configuration. */
|
||||||
readonly settings: SettingsClient;
|
readonly settings: SettingsClient;
|
||||||
|
/** Knowledge filters client for managing filters. */
|
||||||
|
readonly knowledgeFilters: KnowledgeFiltersClient;
|
||||||
|
|
||||||
constructor(options: OpenRAGClientOptions = {}) {
|
constructor(options: OpenRAGClientOptions = {}) {
|
||||||
// Resolve API key from argument or environment
|
// Resolve API key from argument or environment
|
||||||
|
|
@ -108,6 +129,7 @@ export class OpenRAGClient {
|
||||||
this.search = new SearchClient(this);
|
this.search = new SearchClient(this);
|
||||||
this.documents = new DocumentsClient(this);
|
this.documents = new DocumentsClient(this);
|
||||||
this.settings = new SettingsClient(this);
|
this.settings = new SettingsClient(this);
|
||||||
|
this.knowledgeFilters = new KnowledgeFiltersClient(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal Get request headers with authentication. */
|
/** @internal Get request headers with authentication. */
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ export { OpenRAGClient } from "./client";
|
||||||
export { ChatClient, ChatStream } from "./chat";
|
export { ChatClient, ChatStream } from "./chat";
|
||||||
export { SearchClient } from "./search";
|
export { SearchClient } from "./search";
|
||||||
export { DocumentsClient } from "./documents";
|
export { DocumentsClient } from "./documents";
|
||||||
|
export { KnowledgeFiltersClient } from "./knowledge-filters";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// Error types
|
// Error types
|
||||||
|
|
@ -71,6 +72,17 @@ export {
|
||||||
Message,
|
Message,
|
||||||
// Settings types
|
// Settings types
|
||||||
SettingsResponse,
|
SettingsResponse,
|
||||||
|
SettingsUpdateOptions,
|
||||||
|
SettingsUpdateResponse,
|
||||||
AgentSettings,
|
AgentSettings,
|
||||||
KnowledgeSettings,
|
KnowledgeSettings,
|
||||||
|
// Knowledge filter types
|
||||||
|
KnowledgeFilter,
|
||||||
|
KnowledgeFilterQueryData,
|
||||||
|
CreateKnowledgeFilterOptions,
|
||||||
|
UpdateKnowledgeFilterOptions,
|
||||||
|
CreateKnowledgeFilterResponse,
|
||||||
|
KnowledgeFilterSearchResponse,
|
||||||
|
GetKnowledgeFilterResponse,
|
||||||
|
DeleteKnowledgeFilterResponse,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
|
||||||
177
sdks/typescript/src/knowledge-filters.ts
Normal file
177
sdks/typescript/src/knowledge-filters.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
/**
|
||||||
|
* OpenRAG SDK knowledge filters client.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { OpenRAGClient } from "./client";
|
||||||
|
import type {
|
||||||
|
CreateKnowledgeFilterOptions,
|
||||||
|
CreateKnowledgeFilterResponse,
|
||||||
|
DeleteKnowledgeFilterResponse,
|
||||||
|
GetKnowledgeFilterResponse,
|
||||||
|
KnowledgeFilter,
|
||||||
|
KnowledgeFilterSearchResponse,
|
||||||
|
UpdateKnowledgeFilterOptions,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export class KnowledgeFiltersClient {
|
||||||
|
constructor(private client: OpenRAGClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new knowledge filter.
|
||||||
|
*
|
||||||
|
* @param options - The filter options including name and queryData.
|
||||||
|
* @returns The created filter response with ID.
|
||||||
|
*/
|
||||||
|
async create(
|
||||||
|
options: CreateKnowledgeFilterOptions
|
||||||
|
): Promise<CreateKnowledgeFilterResponse> {
|
||||||
|
const body = {
|
||||||
|
name: options.name,
|
||||||
|
description: options.description ?? "",
|
||||||
|
queryData: JSON.stringify(options.queryData),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await this.client._request("POST", "/knowledge-filter", {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
success: data.success ?? false,
|
||||||
|
id: data.id,
|
||||||
|
error: data.error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for knowledge filters by name, description, or query content.
|
||||||
|
*
|
||||||
|
* @param query - Optional search query text.
|
||||||
|
* @param limit - Maximum number of results (default 20).
|
||||||
|
* @returns List of matching knowledge filters.
|
||||||
|
*/
|
||||||
|
async search(query?: string, limit?: number): Promise<KnowledgeFilter[]> {
|
||||||
|
const body = {
|
||||||
|
query: query ?? "",
|
||||||
|
limit: limit ?? 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await this.client._request(
|
||||||
|
"POST",
|
||||||
|
"/knowledge-filter/search",
|
||||||
|
{
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = (await response.json()) as KnowledgeFilterSearchResponse;
|
||||||
|
if (!data.success || !data.filters) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return data.filters.map((f: any) => this._parseFilter(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific knowledge filter by ID.
|
||||||
|
*
|
||||||
|
* @param filterId - The ID of the filter to retrieve.
|
||||||
|
* @returns The knowledge filter or null if not found.
|
||||||
|
*/
|
||||||
|
async get(filterId: string): Promise<KnowledgeFilter | null> {
|
||||||
|
try {
|
||||||
|
const response = await this.client._request(
|
||||||
|
"GET",
|
||||||
|
`/knowledge-filter/${filterId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = (await response.json()) as GetKnowledgeFilterResponse;
|
||||||
|
if (!data.success || !data.filter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return this._parseFilter(data.filter as any);
|
||||||
|
} catch {
|
||||||
|
// Filter not found or other error
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing knowledge filter.
|
||||||
|
*
|
||||||
|
* @param filterId - The ID of the filter to update.
|
||||||
|
* @param options - The fields to update.
|
||||||
|
* @returns Success status.
|
||||||
|
*/
|
||||||
|
async update(
|
||||||
|
filterId: string,
|
||||||
|
options: UpdateKnowledgeFilterOptions
|
||||||
|
): Promise<boolean> {
|
||||||
|
const body: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
if (options.name !== undefined) {
|
||||||
|
body["name"] = options.name;
|
||||||
|
}
|
||||||
|
if (options.description !== undefined) {
|
||||||
|
body["description"] = options.description;
|
||||||
|
}
|
||||||
|
if (options.queryData !== undefined) {
|
||||||
|
body["queryData"] = JSON.stringify(options.queryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.client._request(
|
||||||
|
"PUT",
|
||||||
|
`/knowledge-filter/${filterId}`,
|
||||||
|
{
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.success ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a knowledge filter.
|
||||||
|
*
|
||||||
|
* @param filterId - The ID of the filter to delete.
|
||||||
|
* @returns Success status.
|
||||||
|
*/
|
||||||
|
async delete(filterId: string): Promise<boolean> {
|
||||||
|
const response = await this.client._request(
|
||||||
|
"DELETE",
|
||||||
|
`/knowledge-filter/${filterId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = (await response.json()) as DeleteKnowledgeFilterResponse;
|
||||||
|
return data.success ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a filter from API response, handling JSON-stringified queryData.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
private _parseFilter(filter: any): KnowledgeFilter {
|
||||||
|
let queryData = filter["query_data"] ?? filter["queryData"];
|
||||||
|
if (typeof queryData === "string") {
|
||||||
|
try {
|
||||||
|
queryData = JSON.parse(queryData);
|
||||||
|
} catch {
|
||||||
|
queryData = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: filter["id"] as string,
|
||||||
|
name: filter["name"] as string,
|
||||||
|
description: filter["description"],
|
||||||
|
queryData: queryData as KnowledgeFilter["queryData"],
|
||||||
|
owner: filter["owner"],
|
||||||
|
createdAt: filter["created_at"] ?? filter["createdAt"],
|
||||||
|
updatedAt: filter["updated_at"] ?? filter["updatedAt"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,10 @@ export class SearchClient {
|
||||||
body["filters"] = options.filters;
|
body["filters"] = options.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.filterId) {
|
||||||
|
body["filter_id"] = options.filterId;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await this.client._request("POST", "/api/v1/search", {
|
const response = await this.client._request("POST", "/api/v1/search", {
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,114 @@ export interface SettingsResponse {
|
||||||
knowledge: KnowledgeSettings;
|
knowledge: KnowledgeSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Options for updating settings. */
|
||||||
|
export interface SettingsUpdateOptions {
|
||||||
|
/** LLM model name. */
|
||||||
|
llm_model?: string;
|
||||||
|
/** LLM provider (openai, anthropic, watsonx, ollama). */
|
||||||
|
llm_provider?: string;
|
||||||
|
/** System prompt for the agent. */
|
||||||
|
system_prompt?: string;
|
||||||
|
/** Embedding model name. */
|
||||||
|
embedding_model?: string;
|
||||||
|
/** Embedding provider (openai, watsonx, ollama). */
|
||||||
|
embedding_provider?: string;
|
||||||
|
/** Chunk size for document splitting. */
|
||||||
|
chunk_size?: number;
|
||||||
|
/** Chunk overlap for document splitting. */
|
||||||
|
chunk_overlap?: number;
|
||||||
|
/** Enable table structure parsing. */
|
||||||
|
table_structure?: boolean;
|
||||||
|
/** Enable OCR for text extraction. */
|
||||||
|
ocr?: boolean;
|
||||||
|
/** Enable picture descriptions. */
|
||||||
|
picture_descriptions?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response from settings update. */
|
||||||
|
export interface SettingsUpdateResponse {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Knowledge filter types
|
||||||
|
/** Query configuration stored in a knowledge filter. */
|
||||||
|
export interface KnowledgeFilterQueryData {
|
||||||
|
/** Semantic search query text. */
|
||||||
|
query?: string;
|
||||||
|
/** Filter criteria for documents. */
|
||||||
|
filters?: {
|
||||||
|
data_sources?: string[];
|
||||||
|
document_types?: string[];
|
||||||
|
owners?: string[];
|
||||||
|
connector_types?: string[];
|
||||||
|
};
|
||||||
|
/** Maximum number of results. */
|
||||||
|
limit?: number;
|
||||||
|
/** Minimum relevance score threshold. */
|
||||||
|
scoreThreshold?: number;
|
||||||
|
/** UI color for the filter. */
|
||||||
|
color?: string;
|
||||||
|
/** UI icon for the filter. */
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A knowledge filter definition. */
|
||||||
|
export interface KnowledgeFilter {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
queryData: KnowledgeFilterQueryData;
|
||||||
|
owner?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Options for creating a knowledge filter. */
|
||||||
|
export interface CreateKnowledgeFilterOptions {
|
||||||
|
/** Filter name (required). */
|
||||||
|
name: string;
|
||||||
|
/** Filter description. */
|
||||||
|
description?: string;
|
||||||
|
/** Query configuration for the filter. */
|
||||||
|
queryData: KnowledgeFilterQueryData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Options for updating a knowledge filter. */
|
||||||
|
export interface UpdateKnowledgeFilterOptions {
|
||||||
|
/** New filter name. */
|
||||||
|
name?: string;
|
||||||
|
/** New filter description. */
|
||||||
|
description?: string;
|
||||||
|
/** New query configuration. */
|
||||||
|
queryData?: KnowledgeFilterQueryData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response from creating a knowledge filter. */
|
||||||
|
export interface CreateKnowledgeFilterResponse {
|
||||||
|
success: boolean;
|
||||||
|
id?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response from searching knowledge filters. */
|
||||||
|
export interface KnowledgeFilterSearchResponse {
|
||||||
|
success: boolean;
|
||||||
|
filters: KnowledgeFilter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response from getting a knowledge filter. */
|
||||||
|
export interface GetKnowledgeFilterResponse {
|
||||||
|
success: boolean;
|
||||||
|
filter?: KnowledgeFilter;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response from deleting a knowledge filter. */
|
||||||
|
export interface DeleteKnowledgeFilterResponse {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Client options
|
// Client options
|
||||||
export interface OpenRAGClientOptions {
|
export interface OpenRAGClientOptions {
|
||||||
/** API key for authentication. Falls back to OPENRAG_API_KEY env var. */
|
/** API key for authentication. Falls back to OPENRAG_API_KEY env var. */
|
||||||
|
|
@ -135,6 +243,8 @@ export interface ChatCreateOptions {
|
||||||
filters?: SearchFilters;
|
filters?: SearchFilters;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
scoreThreshold?: number;
|
scoreThreshold?: number;
|
||||||
|
/** Knowledge filter ID to apply to the chat. */
|
||||||
|
filterId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchQueryOptions {
|
export interface SearchQueryOptions {
|
||||||
|
|
@ -142,6 +252,8 @@ export interface SearchQueryOptions {
|
||||||
filters?: SearchFilters;
|
filters?: SearchFilters;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
scoreThreshold?: number;
|
scoreThreshold?: number;
|
||||||
|
/** Knowledge filter ID to apply to the search. */
|
||||||
|
filterId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error types
|
// Error types
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,139 @@ describe.skipIf(SKIP_TESTS)("OpenRAG TypeScript SDK Integration", () => {
|
||||||
expect(settings.agent).toBeDefined();
|
expect(settings.agent).toBeDefined();
|
||||||
expect(settings.knowledge).toBeDefined();
|
expect(settings.knowledge).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should update settings", async () => {
|
||||||
|
// Get current settings first
|
||||||
|
const currentSettings = await client.settings.get();
|
||||||
|
const currentChunkSize = currentSettings.knowledge.chunk_size || 1000;
|
||||||
|
|
||||||
|
// Update with a new value
|
||||||
|
const result = await client.settings.update({
|
||||||
|
chunk_size: currentChunkSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.message).toBeDefined();
|
||||||
|
|
||||||
|
// Verify the setting persisted
|
||||||
|
const updatedSettings = await client.settings.get();
|
||||||
|
expect(updatedSettings.knowledge.chunk_size).toBe(currentChunkSize);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Knowledge Filters", () => {
|
||||||
|
let createdFilterId: string;
|
||||||
|
|
||||||
|
it("should create a knowledge filter", async () => {
|
||||||
|
const result = await client.knowledgeFilters.create({
|
||||||
|
name: "SDK Test Filter",
|
||||||
|
description: "Filter created by TypeScript SDK integration tests",
|
||||||
|
queryData: {
|
||||||
|
query: "test documents",
|
||||||
|
limit: 10,
|
||||||
|
scoreThreshold: 0.5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.id).toBeDefined();
|
||||||
|
createdFilterId = result.id!;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should search knowledge filters", async () => {
|
||||||
|
const filters = await client.knowledgeFilters.search("SDK Test");
|
||||||
|
|
||||||
|
expect(Array.isArray(filters)).toBe(true);
|
||||||
|
// Should find the filter we created
|
||||||
|
const found = filters.some((f) => f.name === "SDK Test Filter");
|
||||||
|
expect(found).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get a knowledge filter by ID", async () => {
|
||||||
|
expect(createdFilterId).toBeDefined();
|
||||||
|
|
||||||
|
const filter = await client.knowledgeFilters.get(createdFilterId);
|
||||||
|
|
||||||
|
expect(filter).not.toBeNull();
|
||||||
|
expect(filter!.id).toBe(createdFilterId);
|
||||||
|
expect(filter!.name).toBe("SDK Test Filter");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update a knowledge filter", async () => {
|
||||||
|
expect(createdFilterId).toBeDefined();
|
||||||
|
|
||||||
|
const success = await client.knowledgeFilters.update(createdFilterId, {
|
||||||
|
description: "Updated description from SDK test",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(success).toBe(true);
|
||||||
|
|
||||||
|
// Verify the update
|
||||||
|
const filter = await client.knowledgeFilters.get(createdFilterId);
|
||||||
|
expect(filter!.description).toBe("Updated description from SDK test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should delete a knowledge filter", async () => {
|
||||||
|
expect(createdFilterId).toBeDefined();
|
||||||
|
|
||||||
|
const success = await client.knowledgeFilters.delete(createdFilterId);
|
||||||
|
|
||||||
|
expect(success).toBe(true);
|
||||||
|
|
||||||
|
// Verify deletion
|
||||||
|
const filter = await client.knowledgeFilters.get(createdFilterId);
|
||||||
|
expect(filter).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use filterId in chat", async () => {
|
||||||
|
// Create a filter first
|
||||||
|
const createResult = await client.knowledgeFilters.create({
|
||||||
|
name: "Chat Test Filter",
|
||||||
|
description: "Filter for testing chat with filterId",
|
||||||
|
queryData: {
|
||||||
|
query: "test",
|
||||||
|
limit: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(createResult.success).toBe(true);
|
||||||
|
const filterId = createResult.id!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use filter in chat
|
||||||
|
const response = await client.chat.create({
|
||||||
|
message: "Hello with filter",
|
||||||
|
filterId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.response).toBeDefined();
|
||||||
|
} finally {
|
||||||
|
// Cleanup
|
||||||
|
await client.knowledgeFilters.delete(filterId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use filterId in search", async () => {
|
||||||
|
// Create a filter first
|
||||||
|
const createResult = await client.knowledgeFilters.create({
|
||||||
|
name: "Search Test Filter",
|
||||||
|
description: "Filter for testing search with filterId",
|
||||||
|
queryData: {
|
||||||
|
query: "test",
|
||||||
|
limit: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(createResult.success).toBe(true);
|
||||||
|
const filterId = createResult.id!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use filter in search
|
||||||
|
const results = await client.search.query("test query", { filterId });
|
||||||
|
|
||||||
|
expect(results.results).toBeDefined();
|
||||||
|
} finally {
|
||||||
|
// Cleanup
|
||||||
|
await client.knowledgeFilters.delete(filterId);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Documents", () => {
|
describe("Documents", () => {
|
||||||
|
|
@ -221,5 +354,34 @@ describe.skipIf(SKIP_TESTS)("OpenRAG TypeScript SDK Integration", () => {
|
||||||
expect(result.conversations).toBeDefined();
|
expect(result.conversations).toBeDefined();
|
||||||
expect(Array.isArray(result.conversations)).toBe(true);
|
expect(Array.isArray(result.conversations)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should get a specific conversation", async () => {
|
||||||
|
// Create a conversation first
|
||||||
|
const response = await client.chat.create({
|
||||||
|
message: "Test message for get.",
|
||||||
|
});
|
||||||
|
expect(response.chatId).toBeDefined();
|
||||||
|
|
||||||
|
// Get the conversation
|
||||||
|
const conversation = await client.chat.get(response.chatId!);
|
||||||
|
|
||||||
|
expect(conversation.chatId).toBe(response.chatId);
|
||||||
|
expect(conversation.messages).toBeDefined();
|
||||||
|
expect(Array.isArray(conversation.messages)).toBe(true);
|
||||||
|
expect(conversation.messages.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should delete a conversation", async () => {
|
||||||
|
// Create a conversation first
|
||||||
|
const response = await client.chat.create({
|
||||||
|
message: "Test message for delete.",
|
||||||
|
});
|
||||||
|
expect(response.chatId).toBeDefined();
|
||||||
|
|
||||||
|
// Delete the conversation
|
||||||
|
const result = await client.chat.delete(response.chatId!);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue