NAME

Langertha::ToolResult - Immutable canonical result of executing one tool, with cross-provider conversion

VERSION

version 0.504

SYNOPSIS

use Langertha::ToolResult;

my $result = Langertha::ToolResult->new(
    name     => 'get_weather',
    id       => 'call_abc',
    content  => [ { type => 'text', text => 'Sunny, 22C' } ],
    is_error => 0,
);

my $block = $result->to('anthropic');
# { type => 'tool_result', tool_use_id => 'call_abc', content => [...] }

DESCRIPTION

Canonical, provider-neutral result of a single tool execution. Serializes to the per-provider result block via to($fmt) — one block per result. The surrounding message envelope (arity, the assistant echo of the prior turn) is assembled by Langertha::Role::Tools, not here: a ToolResult knows only its own block shape.

The content is the MCP-style content array ([ { type = 'text', text => ... } ]>). OpenAI, OpenAI Responses, Ollama and Hermes send it as one string: text parts joined with "\n", an embedded text resource as its text, a text/* blob decoded as UTF-8, a resource_link as [resource_link] name <uri>, and an image, audio or binary blob as a placeholder such as [image] image/png (12345 bytes) -- never the base64 payload. Gemini sends that string as { result => ... }, or the "structured_content" object itself when there is one.

Anthropic takes structured blocks, so each MCP block is mapped onto one: text keeps only text (and cache_control); an image, or an embedded resource whose blob is an image, becomes a base64 image (JPEG, PNG, GIF, WebP) when to is called with image_input => 1 (see below), else the text placeholder; a PDF blob becomes a base64 document (unless to is called with native_pdf => 0, see below); a text resource (whatever its MIME type) or a text/* blob becomes a text document. Anthropic-native image / document blocks (with a source) and search_result pass through. Everything else -- resource_link, audio, other MIME types -- becomes the same text placeholder, naming type, MIME type, URI and size.

With source_blocks => 0 the Anthropic form sends no document or search_result block, for a server that rejects them inside a tool_result: a text resource or text/* blob becomes a text block holding the text (as on the string wires), a PDF blob the placeholder, and an Anthropic-native document or search_result a text block with the text it has in the string form (below). "format_tool_results" in Langertha::Role::Tools passes it on the /anthropic shims of AKI.IO, Moonshot and (conservatively, not live-verified) LM Studio.

An Anthropic-native block on a string wire (or with source_blocks => 0) keeps its text: a document with a text source gives that text, one with a content source its inner parts joined with "\n" (an image among them as a placeholder), a search_result a line [search_result] title <source> followed by its text parts. Anything else becomes a placeholder.

On every string wire and on Anthropic, empty content goes out as the JSON-encoded "structured_content", or as ''.

Three wires carry an image natively ("carries_native"), and do so only when to is called with image_input => 1 ("format_tool_results" in Langertha::Role::Tools passes it when the engine claims tool_result_images for the selected model, ADR 0039):

  • anthropic -- the base64 image block above. Documented for first-party Anthropic; on the /anthropic shims it is not live-verified: Kimi's schema lists image inside a tool_result (docs only), MiniMax documents nothing about it, and the only live evidence is negative (AKI.IO's shim accepts it but its models do not see it, so that engine never sends it).

  • responses -- output becomes an array of parts in content order: an image (image block or image resource; JPEG, PNG, GIF, WebP) is an input_image with a data: URL in image_url, every other item an input_text with the text it has in the string form. Without such an image output stays the string. OpenAI /v1/responses and Perplexity /v1/agent document the same shape.

  • gemini -- an image (JPEG, PNG, WebP) moves out of the result string into functionResponse.parts[].inlineData (mimeType, data). Documented for Gemini 3 and later (v1beta).

The responses and gemini forms are documentation-derived, not live-verified. openai, ollama and hermes have no image form in a tool result and ignore the option: the image stays a placeholder there.

A PDF (an embedded resource whose blob is application/pdf) rides natively on two more of those wires when to is called with native_pdf => 1 ("format_tool_results" in Langertha::Role::Tools passes it when the engine claims tool_result_pdf: OpenAI Responses and Gemini 3 and later, for a model that claims image_input):

  • responses -- output becomes the part array as above, the PDF an input_file with file_data (a data:application/pdf;base64,... URL) and a filename: the percent-decoded last segment of the resource URI's hierarchical path, with .pdf appended when missing; an opaque URI (urn:, data:) or an empty path gives document.pdf. Documented for OpenAI /v1/responses; Perplexity's /v1/agent takes no input_file, so the PDF stays the placeholder there.

  • gemini -- the PDF moves out of the result string into functionResponse.parts[].inlineData with mimeType application/pdf. Documented for Gemini 3 and later.

Both are documentation-derived, not live-verified. On anthropic, native_pdf defaults to true: the PDF document block goes out unless native_pdf => 0 (or source_blocks => 0) is passed, which sends the placeholder instead and leaves text documents and search_result blocks alone. "format_tool_results" in Langertha::Role::Tools passes the engine's tool_result_pdf there. openai, ollama and hermes ignore native_pdf and keep the placeholder.

Not every block is a chat message: the OpenAI Responses block is an input item discriminated by type (function_call_output), carrying its payload in output and no role at all.

name

The tool's name. Used by formats that key results by name (Gemini, Hermes, Ollama's tool_name).

id

The provider call id this result answers (tool_call_id / tool_use_id / call_id; Gemini's functionResponse.id, Ollama's tool_call_id). May be empty; Gemini and Ollama then send no id at all.

content

The MCP-style content array of the tool's output, e.g. [ { type = 'text', text => '...' } ]>.

is_error

Boolean. True when the tool execution failed; surfaced on formats that carry an error flag (Anthropic is_error).

structured_content

The MCP structuredContent of the tool's output, if any. Sent JSON-encoded as the result string when content is empty; Gemini sends the object as its functionResponse.response whenever it is present.

to

my $block = $result->to($fmt);
my $block = $result->to('hermes', response_tag => 'fn_response');
my $block = $result->to('responses', image_input => 1);
my $block = $result->to('gemini', image_input => 1, native_pdf => 1);

Serializes to the result block for the given tool_wire_format. Extra options are passed through to the per-format serializer (Hermes accepts response_tag; responses, gemini and anthropic accept image_input and native_pdf (default true on anthropic, false elsewhere), anthropic also source_blocks, see "DESCRIPTION"; the other formats ignore them).

carries_native

Langertha::ToolResult->carries_native( 'gemini', 'image' );   # 1
Langertha::ToolResult->carries_native( 'openai', 'pdf' );     # 0

True when the result block of the given tool_wire_format has a native form for a media kind (image or pdf) that a tool returned: anthropic, responses and gemini for both. On every other wire (and an unknown or undefined one) it is false: the item goes out as the text placeholder whatever option is passed to "to". Croaks on an unknown kind.

This is the wire's half only. Whether an engine sends the native form for the selected model is its tool_result_images / tool_result_pdf capability ("engine_capabilities" in Langertha::Role::Capabilities), which requires it.

native_needs_vision

Langertha::ToolResult->native_needs_vision( 'responses', 'pdf' );   # 1
Langertha::ToolResult->native_needs_vision( 'anthropic', 'pdf' );   # 0

True when the native form of a media kind on the given wire reaches only a model that sees images (image_input): an image on every wire that has a form, a PDF on responses and gemini (both providers read PDFs through the model's vision). False for a PDF on anthropic, which a text-only model reads as extracted text (k371), and on a wire without a native form (see "carries_native"). Croaks on an unknown kind. The capability registry clears tool_result_images / tool_result_pdf by it (ADR 0039).

SEE ALSO

SUPPORT

Issues

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

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.