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
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,53 @@ print(result.namespace) # "default" or your explicit namespace
print(result.document_id) # Published canonical document id
```

### Bring your own LLM keys (BYOK)

Pass OpenAI-compatible credentials for parsing or agentic retrieval.

Flat root applies to both channels (one multimodal model). Use `models` for
different model ids on the same endpoint, or `text` / `vision` for different
provider endpoints:

```python
# Multimodal shorthand — one model for text + vision
llm_config = {
"api_key": "sk-...",
"model": "gpt-4o",
"base_url": "https://api.openai.com/v1",
}

# Same endpoint, different models per channel
llm_config = {
"api_key": "sk-...",
"base_url": "https://api.openai.com/v1",
"models": {"text": "gpt-4o-mini", "vision": "gpt-4o"},
}

# Or two different endpoints
llm_config = {
"text": {
"api_key": "sk-...",
"model": "gpt-4o-mini",
"base_url": "https://api.openai.com/v1",
},
"vision": {
"api_key": "sk-ali-...",
"model": "qwen-vl-max",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
}

result = client.parse(file=Path("report.pdf"), llm_config=llm_config)

response = client.retrieval.query(
namespace="support-center",
query="refund policy",
use_agentic=True,
llm_config=llm_config,
)
```

### Access different chunk types

```python
Expand Down
21 changes: 21 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ result = client.parse(file=pdf_bytes, file_name="report.pdf")
| `data_id` | `str \| None` | `None` | Your own correlation/idempotency identifier (max 128 chars). |
| `parsing_params` | `ParsingParams \| None` | `None` | Parsing configuration (see below). |
| `webhook` | `WebhookConfig \| None` | `None` | Webhook for completion notification. |
| `llm_config` | `LLMConfig \| None` | `None` | BYOK OpenAI-compatible credentials (flat root and/or `text` / `vision`). |
| `poll_interval` | `float` | `10.0` | Initial polling interval in seconds. |
| `poll_timeout` | `float` | `1800.0` | Maximum time to wait for completion (30 min). |
| `verify_checksum` | `bool` | `True` | Verify SHA-256 checksum of the downloaded ZIP. |
Expand Down Expand Up @@ -197,6 +198,24 @@ result = client.parse(
)
```

### Bring your own LLM keys (BYOK)

Pass OpenAI-compatible credentials via `llm_config` on `parse()`, `jobs.create()`,
or `retrieval.query()`. Flat root applies to both channels; use `models` for
different model ids on the same endpoint, or `text` / `vision` for different
provider endpoints:

```python
result = client.parse(
file=Path("report.pdf"),
llm_config={
"api_key": "sk-...",
"base_url": "https://api.openai.com/v1",
"models": {"text": "gpt-4o-mini", "vision": "gpt-4o"},
},
)
```

---

## Working with Results
Expand Down Expand Up @@ -379,6 +398,7 @@ print(result.statistics)
| `data_id` | `str \| None` | `None` | Your own correlation/idempotency identifier. |
| `parsing_params` | `ParsingParams \| None` | `None` | Parsing configuration. |
| `webhook` | `WebhookConfig \| None` | `None` | Webhook for completion notification. |
| `llm_config` | `LLMConfig \| None` | `None` | BYOK OpenAI-compatible credentials (flat root and/or `text` / `vision`). |

Returns a `Job` object:

Expand Down Expand Up @@ -536,6 +556,7 @@ for result in response.results:
| `use_agentic` | `bool \| None` | `None` | Force agentic (`True`) or legacy (`False`) retrieval. `None` uses server default. |
| `chunk_types` | `list["text" \| "image" \| "table" \| "page"] \| None` | `None` | Restrict retrieval to the selected chunk types. |
| `data_type` | `int \| None` | `None` | Deprecated server selector; `7` maps to page chunks and `8` maps to text+image+table. Prefer `chunk_types`. |
| `llm_config` | `LLMConfig \| None` | `None` | BYOK OpenAI-compatible credentials for agentic retrieval (flat root and/or `text` / `vision`). |

Retrieval results expose `content`, not the older parse-result `text` field.
Media results may include `asset_url` when the server can sign the referenced
Expand Down
11 changes: 10 additions & 1 deletion src/knowhere/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@
DocumentListResponse,
)
from knowhere.types.job import Job, JobError, JobProgress, JobResult
from knowhere.types.params import ParsingParams, WebhookConfig
from knowhere.types.params import (
LLMConfig,
LLMModelsConfig,
LLMProviderConfig,
ParsingParams,
WebhookConfig,
)
from knowhere.types.retrieval import (
RetrievalChannel,
RetrievalChunkType,
Expand Down Expand Up @@ -145,6 +151,9 @@
"TableChunk",
"Chunk",
# Param types
"LLMConfig",
"LLMModelsConfig",
"LLMProviderConfig",
"ParsingParams",
"WebhookConfig",
# Callback types
Expand Down
12 changes: 11 additions & 1 deletion src/knowhere/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from knowhere.resources.jobs import AsyncJobs, Jobs
from knowhere.resources.retrieval import AsyncRetrieval, Retrieval
from knowhere.types.job import Job, JobResult
from knowhere.types.params import ParsingParams, WebhookConfig
from knowhere.types.params import LLMConfig, ParsingParams, WebhookConfig
from knowhere.types.result import ParseResult

_logger = getLogger()
Expand Down Expand Up @@ -66,6 +66,7 @@ def parse(
document_id: Optional[str] = ...,
parsing_params: Optional[ParsingParams] = ...,
webhook: Optional[WebhookConfig] = ...,
llm_config: Optional[LLMConfig] = ...,
poll_interval: float = ...,
poll_timeout: float = ...,
verify_checksum: bool = ...,
Expand All @@ -84,6 +85,7 @@ def parse(
document_id: Optional[str] = ...,
parsing_params: Optional[ParsingParams] = ...,
webhook: Optional[WebhookConfig] = ...,
llm_config: Optional[LLMConfig] = ...,
poll_interval: float = ...,
poll_timeout: float = ...,
verify_checksum: bool = ...,
Expand All @@ -102,6 +104,7 @@ def parse(
document_id: Optional[str] = None,
parsing_params: Optional[ParsingParams] = None,
webhook: Optional[WebhookConfig] = None,
llm_config: Optional[LLMConfig] = None,
poll_interval: float = DEFAULT_POLL_INTERVAL,
poll_timeout: float = DEFAULT_POLL_TIMEOUT,
verify_checksum: bool = True,
Expand All @@ -127,6 +130,7 @@ def parse(
document_id=document_id,
parsing_params=parsing_params,
webhook=webhook,
llm_config=llm_config,
)
else:
resolved_name: Optional[str] = file_name
Expand All @@ -140,6 +144,7 @@ def parse(
document_id=document_id,
parsing_params=parsing_params,
webhook=webhook,
llm_config=llm_config,
)
assert file is not None
self.jobs.upload(job, file, on_progress=on_upload_progress)
Expand Down Expand Up @@ -194,6 +199,7 @@ async def parse(
document_id: Optional[str] = ...,
parsing_params: Optional[ParsingParams] = ...,
webhook: Optional[WebhookConfig] = ...,
llm_config: Optional[LLMConfig] = ...,
poll_interval: float = ...,
poll_timeout: float = ...,
verify_checksum: bool = ...,
Expand All @@ -212,6 +218,7 @@ async def parse(
document_id: Optional[str] = ...,
parsing_params: Optional[ParsingParams] = ...,
webhook: Optional[WebhookConfig] = ...,
llm_config: Optional[LLMConfig] = ...,
poll_interval: float = ...,
poll_timeout: float = ...,
verify_checksum: bool = ...,
Expand All @@ -230,6 +237,7 @@ async def parse(
document_id: Optional[str] = None,
parsing_params: Optional[ParsingParams] = None,
webhook: Optional[WebhookConfig] = None,
llm_config: Optional[LLMConfig] = None,
poll_interval: float = DEFAULT_POLL_INTERVAL,
poll_timeout: float = DEFAULT_POLL_TIMEOUT,
verify_checksum: bool = True,
Expand All @@ -251,6 +259,7 @@ async def parse(
document_id=document_id,
parsing_params=parsing_params,
webhook=webhook,
llm_config=llm_config,
)
else:
resolved_name: Optional[str] = file_name
Expand All @@ -264,6 +273,7 @@ async def parse(
document_id=document_id,
parsing_params=parsing_params,
webhook=webhook,
llm_config=llm_config,
)
assert file is not None
await self.jobs.upload(job, file, on_progress=on_upload_progress)
Expand Down
9 changes: 8 additions & 1 deletion src/knowhere/resources/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from knowhere.lib.upload import asyncUploadFile, syncUploadFile
from knowhere.resources._base import AsyncAPIResource, SyncAPIResource
from knowhere.types.job import Job, JobResult
from knowhere.types.params import ParsingParams, WebhookConfig
from knowhere.types.params import LLMConfig, ParsingParams, WebhookConfig
from knowhere.types.result import ParseResult

_logger = getLogger()
Expand All @@ -39,6 +39,7 @@ def create(
data_id: Optional[str] = None,
parsing_params: Optional[ParsingParams] = None,
webhook: Optional[WebhookConfig] = None,
llm_config: Optional[LLMConfig] = None,
) -> Job:
"""Create a new parsing job.

Expand All @@ -51,6 +52,7 @@ def create(
data_id: Optional idempotency / correlation identifier.
parsing_params: Optional parsing configuration.
webhook: Optional webhook configuration.
llm_config: Optional BYOK LLM credentials (OpenAI-compatible).

Returns:
A ``Job`` object with upload details if ``source_type="file"``.
Expand All @@ -70,6 +72,8 @@ def create(
body["parsing_params"] = dict(parsing_params)
if webhook is not None:
body["webhook"] = dict(webhook)
if llm_config is not None:
body["llm_config"] = dict(llm_config)

return self._request(
"POST",
Expand Down Expand Up @@ -195,6 +199,7 @@ async def create(
data_id: Optional[str] = None,
parsing_params: Optional[ParsingParams] = None,
webhook: Optional[WebhookConfig] = None,
llm_config: Optional[LLMConfig] = None,
) -> Job:
"""Create a new parsing job (async)."""
body: Dict[str, Any] = {"source_type": source_type}
Expand All @@ -212,6 +217,8 @@ async def create(
body["parsing_params"] = dict(parsing_params)
if webhook is not None:
body["webhook"] = dict(webhook)
if llm_config is not None:
body["llm_config"] = dict(llm_config)

return await self._request(
"POST",
Expand Down
7 changes: 7 additions & 0 deletions src/knowhere/resources/retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Any, Dict, Optional

from knowhere.resources._base import AsyncAPIResource, SyncAPIResource
from knowhere.types.params import LLMConfig
from knowhere.types.retrieval import (
RetrievalChunkType,
RetrievalChannel,
Expand Down Expand Up @@ -35,6 +36,7 @@ def query(
internal_recall_k: Optional[int] = None,
exclude_document_ids: Optional[list[str]] = None,
exclude_sections: Optional[list[RetrievalSectionExclusion]] = None,
llm_config: Optional[LLMConfig] = None,
) -> RetrievalQueryResponse:
"""Query published documents in a namespace."""
body: Dict[str, Any] = {"query": query}
Expand Down Expand Up @@ -66,6 +68,8 @@ def query(
body["exclude_document_ids"] = exclude_document_ids
if exclude_sections is not None:
body["exclude_sections"] = exclude_sections
if llm_config is not None:
body["llm_config"] = dict(llm_config)

return self._request(
"POST",
Expand Down Expand Up @@ -96,6 +100,7 @@ async def query(
internal_recall_k: Optional[int] = None,
exclude_document_ids: Optional[list[str]] = None,
exclude_sections: Optional[list[RetrievalSectionExclusion]] = None,
llm_config: Optional[LLMConfig] = None,
) -> RetrievalQueryResponse:
"""Query published documents in a namespace."""
body: Dict[str, Any] = {"query": query}
Expand Down Expand Up @@ -127,6 +132,8 @@ async def query(
body["exclude_document_ids"] = exclude_document_ids
if exclude_sections is not None:
body["exclude_sections"] = exclude_sections
if llm_config is not None:
body["llm_config"] = dict(llm_config)

return await self._request(
"POST",
Expand Down
11 changes: 10 additions & 1 deletion src/knowhere/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
DocumentListResponse,
)
from knowhere.types.job import Job, JobError, JobResult
from knowhere.types.params import ParsingParams, WebhookConfig
from knowhere.types.params import (
LLMConfig,
LLMModelsConfig,
LLMProviderConfig,
ParsingParams,
WebhookConfig,
)
from knowhere.types.retrieval import (
RetrievalChannel,
RetrievalChunkType,
Expand Down Expand Up @@ -68,6 +74,9 @@
"RetrievalQueryResponse",
"RetrievalResult",
# params
"LLMConfig",
"LLMModelsConfig",
"LLMProviderConfig",
"ParsingParams",
"WebhookConfig",
# result
Expand Down
31 changes: 31 additions & 0 deletions src/knowhere/types/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@ class ParsingParams(TypedDict, total=False):
add_frag_desc: bool


class LLMProviderConfig(TypedDict, total=False):
"""OpenAI-compatible provider credentials for a single modality."""

api_key: str
model: str
base_url: str


class LLMModelsConfig(TypedDict, total=False):
"""Per-channel model ids sharing root api_key / base_url."""

text: str
vision: str


class LLMConfig(TypedDict, total=False):
"""Bring-your-own-key LLM credentials.

Flat root (``api_key`` / ``model`` / ``base_url``) applies to both channels.
Use ``models`` for different model ids on the same endpoint, or ``text`` /
``vision`` objects for different provider endpoints.
"""

api_key: str
model: str
base_url: str
models: LLMModelsConfig
text: LLMProviderConfig
vision: LLMProviderConfig


class WebhookConfig(TypedDict, total=False):
"""Webhook configuration for job completion notifications."""

Expand Down
Loading
Loading