feat: add knowledge search explain endpoint

This commit is contained in:
2026-06-22 23:42:01 +08:00
parent cf03b0eafd
commit 27894cc713
9 changed files with 610 additions and 10 deletions
@@ -8,6 +8,8 @@ from yuqei_sdk.ai_platform import (
KnowledgeImportBatchCreate,
KnowledgeReindexRequest,
KnowledgeReindexResult,
KnowledgeSearchExplain,
KnowledgeSearchExplainItem,
KnowledgeSearchResult,
LegalCitation,
LegalQaRequest,
@@ -30,6 +32,8 @@ __all__ = [
"KnowledgeImportBatchCreate",
"KnowledgeReindexRequest",
"KnowledgeReindexResult",
"KnowledgeSearchExplain",
"KnowledgeSearchExplainItem",
"KnowledgeSearchResult",
"LegalCitation",
"LegalQaRequest",
@@ -123,6 +123,39 @@ class KnowledgeSearchResult(BaseModel):
matched_terms: list[str] = Field(default_factory=list)
class KnowledgeSearchExplainItem(BaseModel):
chunk: KnowledgeChunk
score: float = 0.0
keyword_score: float = 0.0
vector_score: float = 0.0
hybrid_score: float = 0.0
retrieval_strategy: str = "hybrid"
matched_terms: list[str] = Field(default_factory=list)
included: bool = False
rank: int | None = None
filter_reason: str | None = None
class KnowledgeSearchExplain(BaseModel):
keyword: str
normalized_terms: list[str] = Field(default_factory=list)
retrieval_strategy: str = "hybrid"
knowledge_base_ids: list[str] = Field(default_factory=list)
limit: int = 5
candidate_limit: int = 20
candidate_count: int = 0
included_count: int = 0
retrieval_backend: str = "json-explain"
embedding_provider: str
embedding_model: str
query_embedding_dimension: int = 0
keyword_threshold: float = 0.0
vector_threshold: float = 0.35
hybrid_keyword_weight: float = 0.55
hybrid_vector_weight: float = 0.45
results: list[KnowledgeSearchExplainItem] = Field(default_factory=list)
class KnowledgeReindexRequest(BaseModel):
document_id: str | None = None
knowledge_base_id: str | None = None
@@ -368,6 +401,32 @@ class AiPlatformClient:
response.raise_for_status()
return [KnowledgeSearchResult.model_validate(item) for item in response.json()]
def explain_knowledge_search(
self,
keyword: str = "",
*,
knowledge_base_id: str | None = None,
limit: int = 5,
candidate_limit: int = 20,
strategy: str = "hybrid",
context: RequestContext | None = None,
) -> KnowledgeSearchExplain:
params: dict[str, Any] = {
"keyword": keyword,
"limit": limit,
"candidate_limit": candidate_limit,
"strategy": strategy,
}
if knowledge_base_id:
params["knowledge_base_id"] = knowledge_base_id
response = self._client.get(
f"{self._api_prefix}/knowledge-chunks/search/explain",
params=params,
headers=(context or RequestContext()).to_headers(),
)
response.raise_for_status()
return KnowledgeSearchExplain.model_validate(response.json())
def list_ai_run_audits(
self,
*,
@@ -284,6 +284,62 @@ def test_ai_platform_client_covers_admin_data_endpoints() -> None:
}
],
)
if request.url.path == "/api/v1/knowledge-chunks/search/explain":
query = parse_qs(request.url.query.decode())
assert query["keyword"] == ["lease"]
assert query["limit"] == ["3"]
assert query["candidate_limit"] == ["9"]
assert query["strategy"] == ["hybrid"]
return httpx.Response(
200,
json={
"keyword": "lease",
"normalized_terms": ["lease"],
"retrieval_strategy": "hybrid",
"knowledge_base_ids": [],
"limit": 3,
"candidate_limit": 9,
"candidate_count": 1,
"included_count": 1,
"retrieval_backend": "json-explain",
"embedding_provider": "local-hash",
"embedding_model": "local-hash-v1",
"query_embedding_dimension": 64,
"keyword_threshold": 0.0,
"vector_threshold": 0.35,
"hybrid_keyword_weight": 0.55,
"hybrid_vector_weight": 0.45,
"results": [
{
"chunk": {
"id": "chunk-1",
"document_id": "doc-1",
"import_batch_id": "kb-batch-1",
"knowledge_base_id": "laws-cn",
"title": "Civil Code",
"source_type": "law",
"reference": "Article 703",
"content": "A lease contract defines rent and use of leased property.",
"chunk_index": 1,
"locator": "chunk-1",
"embedding_provider": "local-hash",
"embedding_model": "local-hash-v1",
"embedding_dimension": 64,
"created_at": "2026-06-22T00:00:00Z",
},
"score": 0.82,
"keyword_score": 0.76,
"vector_score": 0.89,
"hybrid_score": 0.82,
"retrieval_strategy": "hybrid",
"matched_terms": ["lease"],
"included": True,
"rank": 1,
"filter_reason": None,
}
],
},
)
if request.url.path == "/api/v1/audit/ai-runs":
return httpx.Response(
200,
@@ -369,6 +425,10 @@ def test_ai_platform_client_covers_admin_data_endpoints() -> None:
chunk_result = client.search_knowledge_chunks("lease", limit=3)
assert chunk_result[0].score == 0.82
assert chunk_result[0].hybrid_score == 0.82
explain = client.explain_knowledge_search("lease", limit=3, candidate_limit=9)
assert explain.embedding_provider == "local-hash"
assert explain.results[0].included is True
assert explain.results[0].rank == 1
audit = client.list_ai_run_audits(limit=5)[0]
assert audit.operation == "legal_qa"
assert audit.retrieved_chunk_ids == ["chunk-1"]