NAME

Langertha::Knarr::Response - Normalized chat response shared across all Knarr handlers and protocol formatters

VERSION

version 1.101

DESCRIPTION

The single shape every Langertha::Knarr::Handler returns and every Langertha::Knarr::Protocol formatter consumes. Mirrors Langertha::Response but is decoupled from it so non-engine handlers (Langertha::Knarr::Handler::Code, Langertha::Knarr::Handler::A2AClient, Langertha::Knarr::Handler::ACPClient) can produce a Knarr response without going through Langertha first.

"coerce" upgrades all the legacy shapes Knarr handlers used to return — a bare string, a { content => ..., model => ... } hashref, or a stringifiable Langertha::Response — into a proper value object. So existing call sites can pass anything they already had and downstream code can rely on a single API.

content

Plain assistant text. Defaults to empty string.

model

The model id that produced the response, if known.

usage

A Langertha::Usage object with token counts, if the engine reported them. undef for handlers that have no usage data (Code, Passthrough).

A plain HashRef is accepted and upgraded with Langertha::Usage's from_hashLangertha::Response declares its own usage as Maybe[HashRef] and the engines pass the provider's raw JSON hash straight through, so that is the shape every real engine response arrives in. The upgrade normalizes the provider spellings (prompt_tokens / input_tokens / prompt_eval_count, ...), which is what lets the protocol formatters call to_openai_format and friends on whatever any handler produced.

tool_calls

ArrayRef of Langertha::ToolCall objects produced by the engine. Empty arrayref when the response is plain text.

finish_reason

Provider-agnostic stop reason (stop, tool_calls, length, ...). Optional; the protocol formatters fall back to stop / end_turn when undef.

id

The provider-side response id (OpenAI chatcmpl-..., Anthropic msg_...), when the engine reported one. Carried so a Knarr trace can be correlated with the provider's own logs. undef for handlers that have no upstream id.

timing

HashRef of engine-measured durations, mirroring "timing" in Langertha::Response. The two keys every Langertha engine populates are ttft_seconds and total_seconds (Float, seconds); provider-native stage durations (Ollama's load_seconds, prompt_eval_seconds, eval_seconds, ...) may be present too.

Only the routed path has this — it comes from the engine's own measurement inside Langertha. Raw passthrough never produces a Langertha::Response and therefore never a timing; see "Timing sources" in Langertha::Knarr::Tracing for which path reports latency from where.

thinking

Chain-of-thought / reasoning text the engine separated from content (DeepSeek reasoning_content, Anthropic thinking blocks, or Langertha::Role::ThinkTag filtering). Carried because it is model output that content no longer holds — without it the reasoning is lost at the proxy boundary. Recorded into the Langfuse generation metadata; the protocol formatters currently do not emit it.

rate_limit

Optional Langertha::RateLimit object built from the upstream provider's quota headers. Kept as the object; consumers pull the scalar fields they need.

raw

Optional. The provider-native response body, kept around for handlers (passthrough-style) that want to preserve every byte upstream returned.

coerce

my $r = Langertha::Knarr::Response->coerce( $whatever );

Class method. Accepts:

  • an existing Langertha::Knarr::Response — returned as-is.

  • a Langertha::Response — fields lifted via from_langertha_response.

  • any other blessed object that stringifies — used as content.

  • a HashRef — fed to new after key normalization.

  • a plain scalar — used as content.

  • undef — produces an empty response.

This is the single normalization entry point. Handlers can return whatever shape is convenient and the dispatcher coerces once at the boundary.

from_langertha_response

my $r = Langertha::Knarr::Response->from_langertha_response($lresp);

Builds a Knarr response from a Langertha::Response. Carries content, model, usage, tool_calls, finish_reason, id, timing, thinking, rate_limit, and raw across. usage arrives as the provider's raw HashRef and is upgraded to a Langertha::Usage on the way in; see "usage".

Every field is read behind a can() guard so Knarr keeps working against a Langertha release that predates one of them — the older attributes were added over several Langertha versions and timing / rate_limit / thinking are the most recent.

ttft_seconds

Time-to-first-token in seconds (Float) out of "timing", or undef when the engine did not measure it (non-streaming calls, or any handler that is not engine-backed).

total_seconds

Total engine-measured call duration in seconds (Float) out of "timing", or undef.

has_tool_calls

True when tool_calls contains at least one entry.

clone_with

my $r2 = $r->clone_with( model => 'override' );

Returns a new response with the given fields overridden. All other attributes carry through from $self.

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-knarr/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.