@@ -637,6 +637,40 @@ def handler(req: httpx.Request) -> httpx.Response:
637637 await provider .aclose ()
638638
639639
640+ async def test_cohere_rerank_document_echo_shared_rule () -> None :
641+ # §6 (0097): Cohere is on the shared document_echo rule -- a TextDoc object
642+ # echo unwraps to its text; a text-less object (or absent) yields null.
643+ def handler (_req : httpx .Request ) -> httpx .Response :
644+ return httpx .Response (
645+ 200 ,
646+ json = _rerank_body (
647+ results = [
648+ {"index" : 0 , "relevance_score" : 0.9 , "document" : {"text" : "echoed" }},
649+ {"index" : 1 , "relevance_score" : 0.5 , "document" : {"image" : "..." }},
650+ ]
651+ ),
652+ )
653+
654+ provider = _rerank_provider (handler )
655+ response = await provider .rerank ("q" , ["a" , "b" ], config = RerankRuntimeConfig (return_documents = True ))
656+ await provider .aclose ()
657+ assert {r .index : r .document for r in response .results } == {0 : "echoed" , 1 : None }
658+
659+
660+ async def test_cohere_rerank_non_object_document_echo_raises () -> None :
661+ # §6 (0097): a non-object scalar echo is wire corruption -> provider_invalid_response.
662+ def handler (_req : httpx .Request ) -> httpx .Response :
663+ return httpx .Response (
664+ 200 ,
665+ json = _rerank_body (results = [{"index" : 0 , "relevance_score" : 0.9 , "document" : 123 }]),
666+ )
667+
668+ provider = _rerank_provider (handler )
669+ with pytest .raises (ProviderInvalidResponse ):
670+ await provider .rerank ("q" , ["a" ], config = RerankRuntimeConfig (return_documents = True ))
671+ await provider .aclose ()
672+
673+
640674# --- typed-event dispatch ---------------------------------------------------
641675
642676
@@ -1152,6 +1186,19 @@ def handler(_req: httpx.Request) -> httpx.Response:
11521186 await provider .aclose ()
11531187
11541188
1189+ async def test_tei_rerank_non_object_text_echo_raises () -> None :
1190+ # §6 (0097): TEI is on the shared document_echo rule -- its `text` echo is a
1191+ # bare string in practice, but a non-object scalar (wire corruption) raises
1192+ # provider_invalid_response rather than folding to null.
1193+ def handler (_req : httpx .Request ) -> httpx .Response :
1194+ return httpx .Response (200 , json = [{"index" : 0 , "score" : 0.9 , "text" : 123 }])
1195+
1196+ provider = _tei_rerank_provider (handler )
1197+ with pytest .raises (ProviderInvalidResponse ):
1198+ await provider .rerank ("q" , ["a" ], config = RerankRuntimeConfig (return_documents = True ))
1199+ await provider .aclose ()
1200+
1201+
11551202def test_tei_base_url_strips_trailing_slash () -> None :
11561203 provider = TeiEmbeddingProvider (base_url = "http://tei-embed.test/" , model = "m" )
11571204 assert provider .base_url == "http://tei-embed.test"
@@ -1335,9 +1382,10 @@ async def test_jina_rerank_document_echo_unwraps_textdoc_object() -> None:
13351382 # Jina's real /v1/rerank echoes the document as a TextDoc OBJECT
13361383 # ({"text": ...}) when return_documents=true, not a bare string -- its
13371384 # OpenAPI types `document` as anyOf[string, TextDoc, ImageDoc, null]. The
1338- # mapping unwraps either shape onto ScoredDocument.document (§6); a shape
1339- # with no string text (an ImageDoc, a malformed entry) yields null, and the
1340- # verbatim object is preserved on RerankResponse.raw.
1385+ # mapping unwraps either shape onto ScoredDocument.document (§6); a text-less
1386+ # object (an ImageDoc) or an absent/null echo yields null, an empty string is
1387+ # present (surfaced as ""), and the verbatim object is preserved on
1388+ # RerankResponse.raw.
13411389 def handler (_req : httpx .Request ) -> httpx .Response :
13421390 return httpx .Response (
13431391 200 ,
@@ -1347,23 +1395,47 @@ def handler(_req: httpx.Request) -> httpx.Response:
13471395 "results" : [
13481396 {"index" : 0 , "relevance_score" : 0.9 , "document" : {"text" : "doc a" }},
13491397 {"index" : 1 , "relevance_score" : 0.5 , "document" : "doc b" },
1350- {"index" : 2 , "relevance_score" : 0.1 , "document" : {"image" : "..." }},
1398+ {"index" : 2 , "relevance_score" : 0.4 , "document" : {"image" : "..." }},
1399+ {"index" : 3 , "relevance_score" : 0.3 , "document" : "" },
1400+ {"index" : 4 , "relevance_score" : 0.1 , "document" : None },
13511401 ],
13521402 },
13531403 )
13541404
13551405 provider = _jina_rerank_provider (handler )
13561406 config = RerankRuntimeConfig (return_documents = True )
1357- response = await provider .rerank ("q" , ["doc a" , "doc b" , "doc c " ], config = config )
1407+ response = await provider .rerank ("q" , ["a" , "b" , "c" , "d" , "e " ], config = config )
13581408 await provider .aclose ()
13591409
1360- # TextDoc object -> its text; bare string -> itself; non-text shape -> null.
1361- assert {r .index : r .document for r in response .results } == {0 : "doc a" , 1 : "doc b" , 2 : None }
1410+ # TextDoc object -> its text; bare string -> itself; text-less object -> null;
1411+ # empty string -> "" (present, distinct from absent); null -> null.
1412+ echoes = {r .index : r .document for r in response .results }
1413+ assert echoes == {0 : "doc a" , 1 : "doc b" , 2 : None , 3 : "" , 4 : None }
13621414 # The verbatim TextDoc object is still reachable on raw.
13631415 assert isinstance (response .raw , dict )
13641416 assert response .raw ["results" ][0 ]["document" ] == {"text" : "doc a" }
13651417
13661418
1419+ async def test_jina_rerank_non_object_document_echo_raises () -> None :
1420+ # 0097: a non-object scalar echo (number / array / bool -- outside Jina's
1421+ # documented anyOf[string, TextDoc, ImageDoc, null]) is wire corruption and
1422+ # raises provider_invalid_response, NOT folded to null.
1423+ def handler (_req : httpx .Request ) -> httpx .Response :
1424+ return httpx .Response (
1425+ 200 ,
1426+ json = {
1427+ "model" : "jina-reranker-test" ,
1428+ "usage" : {"total_tokens" : 4 },
1429+ "results" : [{"index" : 0 , "relevance_score" : 0.9 , "document" : 123 }],
1430+ },
1431+ )
1432+
1433+ provider = _jina_rerank_provider (handler )
1434+ with pytest .raises (ProviderInvalidResponse ):
1435+ await provider .rerank ("q" , ["doc a" ], config = RerankRuntimeConfig (return_documents = True ))
1436+ await provider .aclose ()
1437+
1438+
13671439async def test_jina_rerank_relevance_score_parse_sorted_and_usage () -> None :
13681440 def handler (_req : httpx .Request ) -> httpx .Response :
13691441 # UNSORTED, Cohere-shaped relevance_score; Jina reports total_tokens.
0 commit comments