NAME
Langertha::Skeid::Protocol::RawBody - The client's JSON body as it was sent, with one top-level value replaced
VERSION
version 0.004
DESCRIPTION
A request on the OpenAI face goes to a node that speaks the same wire, and the only thing Skeid has to change in it is model (ADR 0008: the node is asked for the served model). Decoding the body and encoding it again changes more than that: a Perl hash has no key order, so every object comes out with its keys sorted.
That is not cosmetic. A chat template renders the tool definitions into the prompt in the order they arrive, so a re-encoded request is a different prompt -- a different prefix for the node's prompt cache, and a JSON schema spelled properties, required, type where the client wrote and the model was trained on type, properties, required. One node answered exactly such a prompt with gibberish that the client's own bytes did not produce.
So the body is not rebuilt. It goes upstream as the bytes the client sent, and this module replaces the value of one top-level key in them, touching nothing else: not the order, not the whitespace, not the escapes.
The Anthropic and Ollama faces cannot do that -- their bodies are translated, so the upstream body is new. What reaches the prompt verbatim there is still the tool definitions, and those are carried over: "tool_schemas" takes each tool's JSON schema out of the client's bytes, and "encode_with_schemas" writes it into the translated body as it was, inside a tool definition spelled in the order the function-calling wire spells it.
with_top_level
my $bytes = Langertha::Skeid::Protocol::RawBody::with_top_level($body, model => $served);
Returns $body, a JSON object as bytes, with the value of every top-level model replaced by the JSON string for $served, or undef when the bytes are not a JSON object this scanner can follow or carry no such key. The caller then falls back to encoding the decoded body.
Every occurrence is replaced. A body may spell a key twice; parsers disagree on which one counts, and a node that read the other one would be asked for a model the request was never routed or authorized for.
tool_schemas
my $texts = Langertha::Skeid::Protocol::RawBody::tool_schemas($body, 'input_schema');
my $texts = Langertha::Skeid::Protocol::RawBody::tool_schemas($body, 'function', 'parameters');
The JSON schema of every entry of the body's top-level tools, as the client spelled it: an array of texts, one per tool, undef for a tool without one. The path names the member inside a tool -- input_schema on the Anthropic wire, function.parameters on Ollama's. Returns undef when the body has no tools array.
encode_with_schemas
my $bytes = Langertha::Skeid::Protocol::RawBody::encode_with_schemas($openai_body, $texts);
Encodes a translated chat-completions body with its tool definitions spelled the way the function-calling wire spells them and models have seen them -- type before function, name before description before parameters -- and each schema as the text from "tool_schemas", if that text still says what the translated tool carries. A schema the translation changed, or a tool without a text, is encoded from the data instead.
Returns undef when the body has no tools to spell, or their number does not match the texts: the caller then sends the body encoded as before.
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.