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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ response = client.retrieval.query(

print(response.router_used)
print(response.answer_text)
print(response.evidence) # composed parts to consume
print(response.evidence_text)
print(response.stop_reason)
print(response.failure_reason)
Expand All @@ -84,7 +85,7 @@ for reference in response.referenced_chunks:
print(reference.chunk_id, reference.chunk_type, reference.content_source)
print(reference.metadata, reference.asset_url)

for result in response.results:
for result in response.results: # raw path chunks for debug
print(result.chunk_id, result.chunk_type, result.content_source)
print(result.content)
print(result.score)
Expand Down
5 changes: 3 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,14 +541,15 @@ response = client.retrieval.query(
)
print(response.answer_text) # LLM-generated natural-language answer
print(response.router_used) # "workflow_single_step", "small_kb_all", etc.
print(response.evidence_text) # rendered evidence context, when returned
print(response.evidence) # composed parts to consume
print(response.evidence_text) # text projection of those parts
print(response.stop_reason) # agentic termination reason, when returned
print(response.failure_reason) # no-answer reason, when returned
for ref in response.referenced_chunks:
print(ref.chunk_id, ref.document_id, ref.chunk_type, ref.content_source)
print(ref.section_path, ref.file_path, ref.job_id, ref.asset_url, ref.metadata)

# Legacy results are always available
# results are raw path chunks for debug
for result in response.results:
print(result.chunk_id)
print(result.content)
Expand Down
2 changes: 2 additions & 0 deletions src/knowhere/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
from knowhere.types.retrieval import (
RetrievalChannel,
RetrievalChunkType,
RetrievalEvidencePart,
RetrievalFilterMode,
RetrievalQueryResponse,
RetrievalReferencedChunk,
Expand Down Expand Up @@ -149,6 +150,7 @@
"RetrievalReferencedChunk",
"RetrievalSectionExclusion",
"RetrievalSource",
"RetrievalEvidencePart",
"RetrievalQueryResponse",
"RetrievalResult",
# Result types
Expand Down
2 changes: 2 additions & 0 deletions src/knowhere/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from knowhere.types.retrieval import (
RetrievalChannel,
RetrievalChunkType,
RetrievalEvidencePart,
RetrievalFilterMode,
RetrievalQueryResponse,
RetrievalReferencedChunk,
Expand Down Expand Up @@ -84,6 +85,7 @@
"RetrievalReferencedChunk",
"RetrievalSectionExclusion",
"RetrievalSource",
"RetrievalEvidencePart",
"RetrievalQueryResponse",
"RetrievalResult",
# params
Expand Down
26 changes: 23 additions & 3 deletions src/knowhere/types/retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Any, Literal, Optional, TypedDict
from typing import Annotated, Any, Literal, Optional, TypedDict, Union

from pydantic import BaseModel, Field

Expand All @@ -27,6 +27,23 @@ class RetrievalSource(BaseModel):
section_path: Optional[str] = None


class RetrievalTextPart(BaseModel):
type: Literal["text"]
text: str


class RetrievalImagePart(BaseModel):
type: Literal["image"]
media_type: str
data: str


RetrievalEvidencePart = Annotated[
Union[RetrievalTextPart, RetrievalImagePart],
Field(discriminator="type"),
]


class RetrievalResult(BaseModel):
"""Canonical chunk result returned by ``POST /v2/retrieval/query``."""

Expand Down Expand Up @@ -60,16 +77,19 @@ class RetrievalReferencedChunk(BaseModel):
class RetrievalQueryResponse(BaseModel):
"""Response from ``POST /v2/retrieval/query``.

Three PRIMARY output fields for downstream agent consumption:
Downstream agents consume:

- ``evidence_text``: hierarchical evidence tree for LLM context
- ``evidence``: composed parts (text/HTML and inline images)
- ``evidence_text``: text projection of those parts
- ``results``: raw path chunks for debug
- ``decision_trace``: per-step navigation decisions (includes stop/failure)
- ``referenced_chunks``: structured chunk citations for follow-up queries
"""

namespace: str
query: str
router_used: str
evidence: list[RetrievalEvidencePart] = Field(default_factory=list)
answer_text: Optional[str] = None
referenced_chunks: list[RetrievalReferencedChunk] = Field(default_factory=list)
evidence_text: Optional[str] = None
Expand Down
20 changes: 20 additions & 0 deletions tests/test_retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ def _make_retrieval_response() -> Dict[str, Any]:
"query": "refund policy",
"router_used": "discovery+agent",
"answer_text": "Annual plans may be refunded within 30 days of purchase.",
"evidence": [
{
"type": "text",
"text": "Annual plans may be refunded within 30 days.",
},
{
"type": "image",
"media_type": "image/png",
"data": "abc",
},
],
"evidence_text": "Rendered retrieval evidence",
"stop_reason": "answer_done",
"failure_reason": "insufficient evidence",
Expand Down Expand Up @@ -147,6 +158,11 @@ def test_query_sends_request_and_returns_results(self, sync_client: Any) -> None
assert response.answer_text == ("Annual plans may be refunded within 30 days of purchase.")
assert len(response.referenced_chunks) == 1
assert response.evidence_text == "Rendered retrieval evidence"
assert response.evidence[0].type == "text"
assert response.evidence[0].text == "Annual plans may be refunded within 30 days."
assert response.evidence[1].type == "image"
assert response.evidence[1].media_type == "image/png"
assert not hasattr(response.results[0], "composed")
assert response.stop_reason == "answer_done"
assert response.failure_reason == "insufficient evidence"
assert response.referenced_chunks[0].chunk_id == "chunk_001"
Expand Down Expand Up @@ -329,6 +345,8 @@ def test_agentic_response_fields(self, sync_client: Any) -> None:
assert response.referenced_chunks[0].job_id == "job_123"
assert response.referenced_chunks[0].asset_url == ("https://example.com/assets/chunk_001")
assert response.evidence_text == "Rendered retrieval evidence"
assert response.evidence[1].media_type == "image/png"
assert not hasattr(response.results[0], "composed")
assert response.stop_reason == "answer_done"
assert response.failure_reason == "insufficient evidence"
assert response.decision_trace is not None
Expand All @@ -346,5 +364,7 @@ def test_legacy_response_without_agentic_fields(self, sync_client: Any) -> None:
response = sync_client.retrieval.query(query="refund policy")

assert response.answer_text is None
assert response.evidence == []
assert not hasattr(response.results[0], "composed")
assert response.referenced_chunks == []
assert response.decision_trace is None
Loading