NAME

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

VERSION

version 0.503

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; 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, and do so only when to is called with image_input => 1 ("format_tool_results" in Langertha::Role::Tools passes it when the model claims image_input):

  • 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 the Gemini 3 series (v1beta) only.

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 on OpenAI Responses and Gemini 3, 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 the Gemini 3 series.

Both are documentation-derived, not live-verified. The other wires ignore native_pdf: openai, ollama and hermes keep the placeholder, and anthropic sends its document block whenever source_blocks allows it.

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, responses and gemini also native_pdf, anthropic also source_blocks, see "DESCRIPTION"; the other formats ignore them).

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.