NAME
Langertha::Skeid::Protocol::Rerank - The rerank routes -- relayed as the node speaks, translated for a TEI node
VERSION
version 0.004
DESCRIPTION
Serves POST /v1/rerank and its alias POST /rerank. Reranking has no OpenAI definition; the shape clients speak is the one Cohere set and vLLM, Jina and infinity took over:
{ "model": "...", "query": "...", "documents": [ "...", ... ], "top_n": 3, "return_documents": true }
{ "results": [ { "index": 2, "relevance_score": 0.98, "document": { "text": "..." } }, ... ],
"usage": { "total_tokens": 41 } }
It is a relayed route (ADR 0021): a node that speaks this shape gets the client's body with only model replaced, and its answer goes back untouched. What differs between those nodes is therefore left to differ -- document is { "text": ... } on vLLM and Cohere and a plain string on infinity, vLLM returns the documents whether asked or not.
One kind of node does not speak it at all: Hugging Face text-embeddings-inference (TEI), whose only rerank route is POST /rerank at the server root, taking query and texts and answering a bare array of { index, score, text } in document order, its token count in a response header. A node marked rerank_format: tei is translated here, in both directions -- a translation on the upstream side of the hub, where every other one sits on the client side. This module is the only place that knows either wire: the field names of the request, TEI's names, and the three places a reranker reports tokens.
Everything here is a class method.
routes
my @paths = Langertha::Skeid::Protocol::Rerank->routes;
The client paths of the rerank route, sorted: /rerank and /v1/rerank. Both are one route.
endpoint
The endpoint a rerank request's usage event carries, whichever spelling the client used: /v1/rerank.
upstream_path
The path a rerank request is sent to at the node: /rerank, below the node's /v1 or, for a format "at_server_root" names, at the server root.
formats
my @names = Langertha::Skeid::Protocol::Rerank->formats; # ('tei')
The values a node's rerank_format can take, sorted.
normalize_format
my $format = Langertha::Skeid::Protocol::Rerank->normalize_format($node{rerank_format});
A node's rerank_format as it is kept: lowercased and trimmed, the empty string for none -- the node speaks the client's shape and is relayed. Croaks on a value that is not one of "formats", so a misspelled format fails the config load instead of leaving a TEI node to be called in a dialect it does not speak.
at_server_root
my $root = Langertha::Skeid::Protocol::Rerank->at_server_root($format);
Whether a format's rerank route sits at the server root instead of below /v1. True for tei: a node URL written with or without a trailing /v1 is called at {root}/rerank.
request_problem
my $problem = Langertha::Skeid::Protocol::Rerank->request_problem($c->req->json);
Why a decoded request body cannot be routed, as a message for the client, or undef when it can: a body that is not a JSON object, a model that is not a non-empty string, a query that is not a string, documents that is not a non-empty array. Checked before a node is picked, so none of these costs a slot or a usage event. What a document may be is not checked here -- that is the node's to say, and nodes differ.
document_count
my $count = Langertha::Skeid::Protocol::Rerank->document_count($body);
How many documents a request carries: what the usage event records as documents. Counted off the request, so it is there whatever the node reports.
request_to_upstream
my $upstream = Langertha::Skeid::Protocol::Rerank->request_to_upstream($body, $served_model, $format);
The body sent to the node. Without a format it is the client's own, every field as sent, with model replaced by the served model (an alias tier's, ADR 0008).
For tei it is translated: query as it is, documents as texts -- a document is a string or an object with a string text --, return_documents as return_text and truncate when the client sent them, and nothing else: TEI serves one model and takes no model, and top_n is applied to the answer. A document that is not text, and a top_n that is not a whole number of at least zero, throw a Langertha::Skeid::Protocol::Refusal with a message for the client: such a request cannot be put to this node.
response_from_upstream
my $answer = Langertha::Skeid::Protocol::Rerank->response_from_upstream(
$payload, $headers, $body, $served_model, $format);
What the client gets for the answer of a node that has a format: $payload is the decoded answer, $headers the Mojo::Headers it came with, $body the client's request. For tei, the bare array becomes
{ "model": <served model>,
"results": [ { "index": 2, "relevance_score": 0.98, "document": { "text": "..." } }, ... ],
"usage": { "total_tokens": 41 } }
sorted by score, highest first (equal scores in document order), cut to top_n when that is at least 1 (0, null and no top_n are all of them). document is there when the client asked with return_documents and the node returned the text; usage when the node's x-compute-tokens header carries a count. Dies on an answer that is not such an array -- the proxy answers that as a failed translation. A node without a format is not translated and this is not called for it.
usage
my $usage = Langertha::Skeid::Protocol::Rerank->usage($payload, $headers, $format);
# { prompt_tokens => 41, completion_tokens => 0, total_tokens => 41 }, or undef
The tokens a rerank answer reports, as the usage block of the one dialect Skeid meters (metrics.normalize), or undef when the node reported none. A reranker generates nothing, so what it counts is input: the block carries the count as prompt_tokens and as the total, and the model's input_per_million prices it.
Where the count is read: usage.prompt_tokens, else usage.total_tokens (vLLM, infinity, Jina; older vLLM and Jina report only the total); else meta.tokens.input_tokens (Cohere); for a tei node the x-compute-tokens response header. A value that is not a whole number of at least zero counts as not reported. Cohere's meta.billed_units.search_units is not read: it is not a token count. infinity counts characters, not tokens, unless it runs with lengths_via_tokenize -- Skeid records what the node says.
SEE ALSO
"Rerank route" in Langertha::Skeid::Proxy, Langertha::Skeid::Protocol::Audio, Langertha::Skeid::Protocol, "Pluggable Usage Storage" in Langertha::Skeid
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-skeid/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.