Skip to content

Give native tool loops with submit a plain system message - #389

Draft
deepfates wants to merge 1 commit into
mainfrom
plain-native-tool-prompt
Draft

deepfates wants to merge 1 commit into
mainfrom
plain-native-tool-prompt

Conversation

@deepfates

Copy link
Copy Markdown
Owner

What

Imp.Adapter.Chat now gives a ReAct loop that ends with submit, and whose tools the provider holds natively, a plain system message in place of DSPy's layout.

The condition is exact: the loop's guidance names a finish tool (submit_tool set, no finish_tools), and the step's tool-calls output was taken out of the request because the LM calls tools natively (Imp.Predict), so the step's only output is its text field. Unchanged: loops whose model writes its tool calls as text (their reply is parsed from the markers), finish: :tools loops, plain-text-answer loops, and the JSON and XML adapters.

Why

In that loop nothing is parsed from the text the model writes: the reply is read whole as the step's text output (metadata[:text_field]), and the answer is what submit carries. Yet the system message carried the input and output field lists, a [[ ## name ## ]] template with {placeholders} ending [[ ## completed ## ]], and "In adhering to this structure, your objective is:" with the instructions indented under it. Some models read that scaffold as the harness of a graded test and said so in their reasoning, quoting those lines as the graders' instructions.

Before and after

For Imp.react("intent -> answer, confidence: float", [look]):

Before:

Your input fields are:
1. `intent` (string):
Your output fields are:
1. `next_thought` (string):
All interactions will be structured in the following way, with the appropriate values filled in.

[[ ## intent ## ]]
{intent}

[[ ## next_thought ## ]]
{next_thought}

[[ ## completed ## ]]
In adhering to this structure, your objective is: 
        Given the fields `intent`, produce the fields `answer`, `confidence`.
        You are an Agent. Use the supplied tools to produce `answer`, `confidence` from `intent`.
        The outputs to produce are:
        1. `answer` (string): 
        2. `confidence` (number):
        Call tools when more information is needed.
        When the final answer is ready, call `submit` with `answer`, `confidence`.
        The available tools are: `look`, `submit`.

After:

Given the fields `intent`, produce the fields `answer`, `confidence`.

Each message to you has these parts, each under its heading, such as [[ ## intent ## ]]. A part that doesn't apply to a message is left out.
1. `intent` (string):

Use the tools to read and to act, as many times as you need. Text you write is not sent. It stays in this conversation as your own note.

To finish, call `submit` with `answer` and `confidence`:
1. `answer` (string): 
2. `confidence` (number):

The tools you have: `look`, `submit`.

Details:

  • The instructions come first, cleaned as before (cleandoc and dedent) but not indented (Imp.Adapter.Instructions.plain_text/1).
  • The default "You are an Agent..." line is left out in this layout. A host's agent_line string follows the instructions; nil leaves it out as before.
  • Demos and stored turns in this loop show the thought as plain text, without [[ ## next_thought ## ]] and [[ ## completed ## ]] markers, as the native history already does. A system message that describes no markers should not be followed by demos that use them.
  • "Text you write is not sent" matches what the loop does: under submit, a step's text is recorded and replayed as the model's own message, and a thought-only step is followed by the existing unsent note.

Measurement

A single-step A/B on 41 real first requests of a deployed application built on Imp, across 10 model families on OpenRouter. Only the system message differed; the new one was rendered by this branch from the same signature (the parsed signature rendered on main reproduces each original system message byte for byte). One completion per request per arm, two samples per arm, no tools executed.

  • Reasoning or text that treated the situation as a test, an evaluation or a fiction: 2 of 82 samples with the old layout ("a test of handling constraints?", "a fictional or roleplay scenario"), plus one that called the runtime "the harness"; 0 of 82 with the new one.
  • First action (read, write, end, schedule, none): the old and new layouts agree as often as two samples of the old layout agree with each other (86% across arms, 82% within the old arm).
  • Small n and a single step: this does not show the effect on whole turns, and the model that most often reasoned about being graded in the original observation did so here in 0 of 16 samples under either layout.

Tests

  • test/react_v2_request_shape_test.exs: the exact new layout; the agent line override and nil; a written-call loop keeps DSPy's layout and words.
  • test/react_v2_step_contract_test.exs: with submit, a demo and a stored step are replayed as plain text.
  • test/react_v2_plain_text_answer_test.exs: updated for the submit case.
  • The new tests fail on main. The adapter, ReAct, history, GEPA and documentation test files pass.

Docs: docs/diving-deeper/react.md, docs/diving-deeper/adapters.md, CHANGELOG (Unreleased).

A ReAct loop that ends with submit and whose tools the provider holds
natively parses nothing from the text the model writes, yet its system
message carried DSPy's full layout: field lists, a [[ ## name ## ]]
template with placeholders ending [[ ## completed ## ]], and "In adhering
to this structure, your objective is:". Some models read that scaffold
as the harness of a graded test.

The Chat adapter now gives that loop the program's instructions first, as
written; the parts of each message with their descriptions; that text the
model writes is not sent and stays as its own note; how to finish, with
each output's description; and the tools. A host's agent_line follows the
instructions; the default line is left out. Demos and stored turns show
the thought as plain text. Written-call loops, finish: :tools loops,
plain-text-answer loops and the JSON and XML adapters are unchanged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant