Plain chat adapter: instructions plus field-labelled user content.
format/3 renders a signature and its inputs as a system message, one
user/assistant pair per demo, the conversation history, and a final user
message. Fields are labelled with [[ ## name ## ]] markers.
parse/3 accepts an Imp.Prediction, a map of output fields, or completion
text. Text is split on those markers and the first section for each output
field wins. A completion that does not cover every output field is a parse
error rather than a partial prediction.
One signature-declared exception: signature.metadata[:text_field] names an
output field that takes a completion carrying no marker at all. A native tool
loop asks for a thought and tool calls, and a model that answers a step in
plain text with no tool call has said something and called nothing — the
plain reading of that completion, not a format failure worth a second LM call
through Imp.Adapter.JSON. Remaining outputs take their declared defaults, so
the signature says what an unanswered field means. The exception is narrow on
purpose: the completion must carry no [[ ## field ## ]] line anywhere and
must not be blank, a completion that carried native tool calls is a map rather
than text and never reaches it, and a signature without that metadata parses
exactly as before. Imp.Predict.ReActV2 sets it on its internal step
signature; see that module.
Options to format/3: :demos, :response_instruction, :guidance,
:omit_empty_request, and the renderer seams :output_renderer,
:input_section_renderer, :system_renderer, :tool_result_renderer and
:history_note_renderer,
which let another adapter reuse this message assembly with its own dialect
and let a host bound what a tool result costs in the prompt without changing
what the loop records. :history_note_renderer is the one seam for saying
something about a stored turn rather than re-rendering it: it is consulted
for every history turn, native tool turns included, after that turn's own
messages, and its text becomes one user message right after them — the next
thing the model reads. A note is data about the turn (the answer was not
delivered, the account's allowance ran out), not a rewrite of what happened,
so the record the loop keeps is unchanged. Options outside that list
are ignored; anything that is not a keyword list raises ArgumentError.
Summary
Functions
Renders a tool result as the text a reader should see, whether that reader is the model or a person looking at a host's tool card.
The words for a failed tool call, without the Error: that
format_tool_result/1 puts before them: the text an MCP error result carries,
or one sentence for any other reason.
Functions
Renders a tool result as the text a reader should see, whether that reader is the model or a person looking at a host's tool card.
Successful results format like any other value. A failed result renders as
plain text instead of an Elixir term. An MCP error result is the text its
tool wrote, after Error: unless that text already begins with "error",
since the model is given no other sign that the call failed. Every other
failure is one sentence after Error:: a JSON-RPC error is the server's
message; a call that got no answer says why in words, and unless it was
never sent, that it may have been carried out; a denied call says who
declined it; a crashed tool names itself and its message, and one that
exited says it may have been carried out; an unknown tool, a rejected
argument or a rejected submit says what is wrong with the call; an atom
reason is spelled out; and a structured :reason map reads as its reason
and limit. Only the rendering is plain: the result the loop records keeps
the whole structured error. Hosts that relay Imp tool results over a
protocol boundary should use this so the same words reach the person that
reached the model.
iex> Imp.Adapter.Chat.format_tool_result({:error, {:tool_denied, :post, :client_denied}})
"Error: post was not allowed; the person declined it."
The words for a failed tool call, without the Error: that
format_tool_result/1 puts before them: the text an MCP error result carries,
or one sentence for any other reason.
iex> Imp.Adapter.Chat.tool_error_text({:unknown_tool, "frobnicate"})
"there is no tool named frobnicate."