Skip to content

Commit bf64a1a

Browse files
Add retrieval-provider rerank capability (0060a)
Add the RerankProvider protocol, the RerankResponse / ScoredDocument / RerankUsage / RerankRuntimeConfig types, the typed RerankEvent / RerankFailedEvent, and a Cohere-shape reference reranker (CohereRerankProvider against POST /v2/rerank), mirroring the embedding capability (0059). RerankResponse.usage is nullable from the start per proposal 0093; the reference never fabricates a usage record and does not send return_documents (a no-op on the Cohere v2 wire). Wire the conformance harness (the calls_rerank directive) and un-defer the protocol fixtures 006-012. Observability rendering (the OTel rerank span and the Langfuse Retriever observation) defers to the 0060b follow-on; the bundled observers safe-skip the rerank events for now. conformance.toml 0060 moves to partial.
1 parent fe3000a commit bf64a1a

14 files changed

Lines changed: 1538 additions & 112 deletions

File tree

conformance.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -610,12 +610,11 @@ note = "Retrieval-provider embedding capability. The EmbeddingProvider protocol
610610
# Spec v0.70.0 (proposal 0060). Retrieval-provider rerank protocol — the
611611
# ``RerankProvider`` surface + ``RerankEvent`` / ``RerankFailedEvent`` typed
612612
# variants + OTel ``openarmature.rerank.complete`` span / Langfuse Retriever
613-
# observation / rerank metrics. Sibling to the embedding surface (0059);
614-
# python has not shipped retrieval-provider, so rerank is not-yet. Crossed by
615-
# the v0.70.1 pin (adopted for fixture 110 / 0075); the 11 rerank observability
616-
# fixtures (099-109) defer with it.
613+
# observation / rerank metrics. Sibling to the embedding surface (0059).
617614
[proposals."0060"]
618-
status = "not-yet"
615+
status = "partial"
616+
since = "0.16.0"
617+
note = "Retrieval-provider rerank protocol. The RerankProvider protocol (retrieval-provider §5) + the RerankResponse / ScoredDocument / RerankUsage / RerankRuntimeConfig response types (§6) + the RerankRuntimeConfig runtime config (§2) ship, alongside the CohereRerankProvider reference reranker against POST /v2/rerank (§8.4, Cohere-shape): the request body is {model, query, documents (string array), top_n from top_k}, return_documents is a silent no-op (the Cohere wire has no such field), and max_tokens_per_doc rides the extras pass-through bag. The provider parses {id, model?, results: [{index, relevance_score, document?}], meta.billed_units.{search_units, input_tokens}}, sorts results by relevance_score descending, reads the document echo only when present (never auto-filled), builds a RerankUsage record only when the provider surfaces >= 1 usage figure (else usage = null, never fabricated), and enforces the §6 invariants -- valid index into the input documents, no duplicate index, len(results) <= top_k when supplied -- raising provider_invalid_response otherwise. Pre-send validation raises provider_invalid_request on an empty query, an empty documents list, or top_k <= 0 (top_k MAY exceed len(documents)). The provider dispatches the typed RerankEvent / RerankFailedEvent (graph-engine §6) per rerank() call, mutually exclusive, alongside the §7-category exception on failure; RerankEvent.output_results is populated unconditionally on success (proposal 0089). The §7 categories reuse the shared llm-provider taxonomy. Protocol fixtures 006-012 pass. partial because the observability rendering (OTel §5.5.13 openarmature.rerank.complete span + Langfuse §8.4.7 Retriever observation + the rerank-metrics surface; fixtures 099-109 / 138 / 141 / 142) lands in the 0060b follow-on -- the bundled OTel + Langfuse observers safely skip the rerank events for now. Wire-format details (0090 Cohere rerank fixtures 028-031) are a separate proposal."
619618

620619
# Spec v0.55.0 (proposal 0065; repo pins v0.55.1). Failure-isolation
621620
# cause fidelity at non-node placements (pipeline-utilities §6.3 /

src/openarmature/graph/events.py

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
if TYPE_CHECKING:
3333
from openarmature.llm.messages import ToolCall
3434
from openarmature.llm.response import Usage
35-
from openarmature.retrieval.response import EmbeddingUsage
35+
from openarmature.retrieval.response import EmbeddingUsage, RerankUsage, ScoredDocument
3636

3737
# Sentinel empty metadata mapping for events constructed without a
3838
# live caller-metadata snapshot (test helpers, synthetic events).
@@ -877,6 +877,142 @@ class EmbeddingFailedEvent:
877877
caller_invocation_metadata: Mapping[str, AttributeValue] | None = None
878878

879879

880+
# Spec: realizes graph-engine §6 -- the typed RerankEvent / RerankFailedEvent
881+
# pair (proposal 0060, retrieval-provider rerank capability), the rerank
882+
# sibling to the EmbeddingEvent / EmbeddingFailedEvent pair. Dispatched on the
883+
# observer delivery queue per RerankProvider.rerank() call: the success variant
884+
# after the response is parsed + validated (retrieval-provider §6), the failure
885+
# variant alongside a raised §7 category exception (mutually exclusive per
886+
# call). Scalar fan_out_index / branch_name only, matching the embedding pair
887+
# (the lineage chains arrive uniformly across the provider events with proposal
888+
# 0084). query / documents / request_extras / output_results are payload-
889+
# bearing, populated unconditionally; observer-side privacy gates (OTel
890+
# disable_provider_payload, Langfuse equivalents) apply at rendering, symmetric
891+
# with EmbeddingEvent.
892+
@dataclass(frozen=True)
893+
class RerankEvent:
894+
"""A typed rerank provider call event delivered to observers.
895+
896+
Carries identity, scoping, and outcome data for a successful
897+
``RerankProvider.rerank()`` call. Observer code filters by type
898+
discrimination (``isinstance(event, RerankEvent)``).
899+
900+
The identity / scoping / request-side fields mirror ``EmbeddingEvent``'s
901+
convention; the outcome fields are rerank-specific:
902+
903+
- ``query``: the query string the call was made with; non-nullable,
904+
populated unconditionally (privacy gating is observer-side at
905+
rendering).
906+
- ``documents``: the input documents list; non-nullable, populated
907+
unconditionally. Same privacy posture as ``query``.
908+
- ``document_count``: ``len(documents)``; a convenience field.
909+
- ``top_k``: the caller-supplied ``top_k`` value; ``None`` when the
910+
caller passed ``None``.
911+
- ``result_count``: ``len(output_results)``; a convenience field.
912+
- ``output_results``: the scored results the call returned; populated
913+
unconditionally on the success event (privacy gating is observer-side
914+
at rendering).
915+
- ``response_model`` / ``response_id``: the provider-returned model and
916+
response identifiers; ``None`` when the provider returned none.
917+
- ``usage``: the rerank usage record; ``None`` when the call returned
918+
no usage.
919+
- ``request_params``: the rerank request parameters the caller supplied
920+
(e.g. ``return_documents``). Absence-is-meaningful: only supplied keys
921+
appear; an empty mapping when none.
922+
- ``request_extras``: the runtime-config extras pass-through bag.
923+
- ``active_prompt`` / ``active_prompt_group``: prompt-context snapshots
924+
at rerank-call time; ``None`` outside a binding.
925+
- ``call_id``: a per-call disambiguator, always present, freshly minted
926+
per ``rerank()`` call.
927+
"""
928+
929+
invocation_id: str
930+
correlation_id: str | None
931+
node_name: str
932+
namespace: tuple[str, ...]
933+
attempt_index: int
934+
fan_out_index: int | None
935+
branch_name: str | None
936+
provider: str
937+
model: str
938+
response_id: str | None
939+
response_model: str | None
940+
# RerankUsage is a string-typed forward reference per the TYPE_CHECKING
941+
# import -- keeps the runtime import direction graph -> retrieval off the
942+
# module-load path.
943+
usage: "RerankUsage | None"
944+
latency_ms: float | None
945+
query: str
946+
documents: list[str]
947+
document_count: int
948+
top_k: int | None
949+
result_count: int
950+
# Spec graph-engine §6 (proposal 0089): the output scored results (sourced
951+
# from RerankResponse.results at dispatch). Populated unconditionally on the
952+
# success event -- payload-bearing like query / documents, gated observer-
953+
# side at the rendering boundary. The §8.4.7 rerank output mapping reads
954+
# this field. No output field on RerankFailedEvent (no response).
955+
output_results: list["ScoredDocument"]
956+
request_params: Mapping[str, Any]
957+
request_extras: Mapping[str, Any]
958+
active_prompt: Any
959+
active_prompt_group: Any
960+
call_id: str
961+
caller_invocation_metadata: Mapping[str, AttributeValue] | None = None
962+
963+
964+
# Spec: the failure sibling of RerankEvent (proposal 0060). Dispatched
965+
# whenever RerankProvider.rerank() raises a §7 category exception -- covers
966+
# both the provider-caught path and the pre-send validation raise
967+
# (provider_invalid_request on an empty query / documents list / non-positive
968+
# top_k). Dispatched ALONGSIDE the exception, not in place of it; mutually
969+
# exclusive with RerankEvent on the same call. The response-side fields
970+
# (usage / response_id / response_model / result_count / output_results) are
971+
# absent (no response).
972+
@dataclass(frozen=True)
973+
class RerankFailedEvent:
974+
"""A typed rerank provider call failure event delivered to observers.
975+
976+
Carries identity, scoping, and failure-context data for a ``rerank()``
977+
call that raised a retrieval-provider category exception. Observer code
978+
filters by type discrimination (``isinstance(event, RerankFailedEvent)``).
979+
980+
The identity / scoping / request-side field set mirrors ``RerankEvent``;
981+
the response-side fields are absent. Failure-specific fields:
982+
983+
- ``error_category``: the error category the call raised (one of the
984+
rerank-applicable provider categories). Always present.
985+
- ``error_type``: an optional impl-level / vendor-specific type or code;
986+
``None`` when unavailable.
987+
- ``error_message``: a human-readable message; always present (the empty
988+
string when the exception carried no message).
989+
"""
990+
991+
invocation_id: str
992+
correlation_id: str | None
993+
node_name: str
994+
namespace: tuple[str, ...]
995+
attempt_index: int
996+
fan_out_index: int | None
997+
branch_name: str | None
998+
provider: str
999+
model: str
1000+
latency_ms: float | None
1001+
query: str
1002+
documents: list[str]
1003+
document_count: int
1004+
top_k: int | None
1005+
request_params: Mapping[str, Any]
1006+
request_extras: Mapping[str, Any]
1007+
active_prompt: Any
1008+
active_prompt_group: Any
1009+
call_id: str
1010+
error_category: str
1011+
error_message: str
1012+
error_type: str | None = None
1013+
caller_invocation_metadata: Mapping[str, AttributeValue] | None = None
1014+
1015+
8801016
# Spec: realizes pipeline-utilities §6.3 failure-isolation middleware
8811017
# (proposal 0050). Emitted by FailureIsolationMiddleware when it
8821018
# catches an exception escaping the inner chain and substitutes a
@@ -1045,6 +1181,8 @@ class ToolCallFailedEvent:
10451181
"MetadataAugmentationEvent",
10461182
"NodeEvent",
10471183
"ParallelBranchesEventConfig",
1184+
"RerankEvent",
1185+
"RerankFailedEvent",
10481186
"ToolCallEvent",
10491187
"ToolCallFailedEvent",
10501188
]

src/openarmature/graph/observer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
LlmRetryAttemptEvent,
4646
MetadataAugmentationEvent,
4747
NodeEvent,
48+
RerankEvent,
49+
RerankFailedEvent,
4850
ToolCallEvent,
4951
ToolCallFailedEvent,
5052
)
@@ -67,7 +69,9 @@
6769
# dispatched by FailureIsolationMiddleware when it catches an exception
6870
# escaping the inner chain and substitutes a degraded partial update);
6971
# and EmbeddingEvent / EmbeddingFailedEvent (proposal 0059 typed embedding
70-
# provider call events, dispatched on every EmbeddingProvider.embed()).
72+
# provider call events, dispatched on every EmbeddingProvider.embed());
73+
# and RerankEvent / RerankFailedEvent (proposal 0060 typed rerank provider
74+
# call events, dispatched on every RerankProvider.rerank()).
7175
ObserverEvent = (
7276
NodeEvent
7377
| MetadataAugmentationEvent
@@ -81,6 +85,8 @@
8185
| ToolCallFailedEvent
8286
| EmbeddingEvent
8387
| EmbeddingFailedEvent
88+
| RerankEvent
89+
| RerankFailedEvent
8490
)
8591

8692

src/openarmature/observability/langfuse/observer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
LlmRetryAttemptEvent,
4141
MetadataAugmentationEvent,
4242
NodeEvent,
43+
RerankEvent,
44+
RerankFailedEvent,
4345
ToolCallEvent,
4446
ToolCallFailedEvent,
4547
)
@@ -485,6 +487,13 @@ async def __call__(
485487
if isinstance(event, EmbeddingEvent | EmbeddingFailedEvent):
486488
self._handle_embedding(event)
487489
return
490+
# Proposal 0060 rerank observability (observability §8.4.7): the
491+
# dedicated Langfuse Retriever observation renders from the typed
492+
# rerank events. Not implemented yet (the rerank rendering lands in the
493+
# 0060b follow-on); the observer safely skips the events for now rather
494+
# than falling through to the NodeEvent phase dispatch.
495+
if isinstance(event, RerankEvent | RerankFailedEvent):
496+
return
488497
if event.phase == "started":
489498
self._open_started_observation(event)
490499
elif event.phase == "completed":

src/openarmature/observability/otel/observer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@
110110
LlmRetryAttemptEvent,
111111
MetadataAugmentationEvent,
112112
NodeEvent,
113+
RerankEvent,
114+
RerankFailedEvent,
113115
ToolCallEvent,
114116
ToolCallFailedEvent,
115117
)
@@ -786,6 +788,13 @@ async def __call__(
786788
if isinstance(event, EmbeddingEvent | EmbeddingFailedEvent):
787789
self._handle_embedding(event)
788790
return
791+
# Proposal 0060 rerank observability (observability §5.5.13): the
792+
# openarmature.rerank.complete span + rerank metrics render from the
793+
# typed rerank events. Not implemented yet (the rerank rendering lands
794+
# in the 0060b follow-on); the observer safely skips the events for now
795+
# rather than falling through to the NodeEvent phase dispatch.
796+
if isinstance(event, RerankEvent | RerankFailedEvent):
797+
return
789798
# Proposal 0063 tool-execution observability: emit the
790799
# openarmature.tool.call span from the typed tool events.
791800
# Independent of disable_llm_spans (that flag is scoped to LLM
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,47 @@
11
"""The retrieval-provider capability.
22
3-
The embedding provider protocol, its response types, and the bundled
4-
reference embedding provider. The rerank protocol is a sibling surface on
5-
the same capability.
3+
The embedding + rerank provider protocols, their response types, and the
4+
bundled reference providers (an OpenAI-compatible embedding provider and a
5+
Cohere-shape reranker). Embedding and rerank are sibling surfaces on the same
6+
capability.
67
"""
78

89
from __future__ import annotations
910

1011
from .provider import (
1112
EmbeddingProvider,
13+
RerankProvider,
1214
validate_embedding_input,
1315
validate_embedding_response,
16+
validate_rerank_input,
17+
validate_rerank_response,
1418
)
19+
from .providers.cohere import CohereRerankProvider
1520
from .providers.openai import OpenAIEmbeddingProvider
16-
from .response import EmbeddingResponse, EmbeddingRuntimeConfig, EmbeddingUsage
21+
from .response import (
22+
EmbeddingResponse,
23+
EmbeddingRuntimeConfig,
24+
EmbeddingUsage,
25+
RerankResponse,
26+
RerankRuntimeConfig,
27+
RerankUsage,
28+
ScoredDocument,
29+
)
1730

1831
__all__ = [
32+
"CohereRerankProvider",
1933
"EmbeddingProvider",
2034
"EmbeddingResponse",
2135
"EmbeddingRuntimeConfig",
2236
"EmbeddingUsage",
2337
"OpenAIEmbeddingProvider",
38+
"RerankProvider",
39+
"RerankResponse",
40+
"RerankRuntimeConfig",
41+
"RerankUsage",
42+
"ScoredDocument",
2443
"validate_embedding_input",
2544
"validate_embedding_response",
45+
"validate_rerank_input",
46+
"validate_rerank_response",
2647
]

0 commit comments

Comments
 (0)