Skip to content

feat(adapter): render typed history frames - #52

Open
isaacbmiller wants to merge 1 commit into
isaac/react-v2-pr1-history-raw-dictsfrom
isaac/react-v2-pr2-history-formatting
Open

feat(adapter): render typed history frames#52
isaacbmiller wants to merge 1 commit into
isaac/react-v2-pr1-history-raw-dictsfrom
isaac/react-v2-pr2-history-formatting

Conversation

@isaacbmiller

@isaacbmiller isaacbmiller commented May 20, 2026

Copy link
Copy Markdown

Summary

  • render typed history frames as prior user, assistant, and observation messages
  • avoid duplicating current inputs when a history episode is already open
  • keep legacy dict history formatting working

Stack

Validation

@greptile-apps

greptile-apps Bot commented May 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR moves typed History rendering into History.to_lm_messages, replacing a single flat blob with structured user → assistant → user(observations) chat turns that are stable across subsequent LM calls, and suppresses duplicate inputs when an episode is still open.

  • History.to_lm_messages classifies each frame's inputs/outputs/observations into correctly-roled messages; legacy dict entries are mapped via _entry_to_frame which preserves unknown fields in the assistant turn.
  • Adapter.plan_fields and Adapter.format are updated to call to_lm_messages and clear current-turn inputs when has_open_episode() is true, preventing the earlier duplicate-input problem.
  • Two focused adapter formatting tests cover the open-episode path for ChatAdapter and JSONAdapter.

Confidence Score: 3/5

Safe to merge for the common HistoryFrame-based agentic path; the _format_outputs fallback for legacy dict entries with mixed known/unknown output fields generates a malformed assistant prompt turn.

The new to_lm_messages path works correctly for the primary use case. The _format_outputs fallback branch calls format_assistant_message_content (which already ends with [[ ## completed ## ]]) and then appends a second marker, producing a structurally malformed assistant history turn for legacy dict entries with mixed fields — the exact case the PR promises to handle correctly.

dspy/adapters/types/history.py — specifically the _format_outputs fallback path (lines 159–171)

Important Files Changed

Filename Overview
dspy/adapters/types/history.py Adds to_lm_messages to History, converting typed frames into structured user/assistant/user message triples; the _format_outputs fallback for frames with mixed signature and non-signature output fields produces a double [[ ## completed ## ]] marker.
dspy/adapters/base.py Updates both plan_fields and format to call history.to_lm_messages and suppress duplicate inputs when an episode is open; also replaces {field: None for ...} with dict.fromkeys.
tests/adapters/test_history_formatting.py New focused tests for ChatAdapter and JSONAdapter open-episode formatting; no coverage for the closed-episode multi-turn scenario central to the PR's motivation.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Adapter
    participant History

    Caller->>Adapter: format(signature, demos, inputs)
    Adapter->>History: has_open_episode()
    History-->>Adapter: true / false
    Adapter->>History: to_lm_messages(adapter, signature_without_history)
    loop each frame
        History->>History: _entry_to_frame(signature, entry)
        alt frame.inputs
            History->>Adapter: format_user_message_content(inputs)
            Adapter-->>History: user message
        end
        alt frame.outputs
            History->>History: _format_outputs(adapter, signature, outputs)
            History->>Adapter: format_assistant_message_content(signature_outputs)
            Adapter-->>History: assistant content
            History-->>History: assistant message
        end
        alt frame.observations
            History->>History: _format_observations(observations)
            History-->>History: user message (observations)
        end
    end
    History-->>Adapter: list[LMMessage]
    alt has_open_episode
        Adapter->>Adapter: clear current inputs_copy
    end
    Adapter->>Adapter: "format_user_message_content({}, main_request=True)"
    Note over Adapter: Respond with... appended as final user message
    Adapter-->>Caller: [system, ...history turns..., user(Respond with...)]
Loading

Reviews (3): Last reviewed commit: "feat(adapter): render typed history fram..." | Re-trigger Greptile

Comment thread dspy/adapters/base.py Outdated
Comment thread dspy/adapters/base.py Outdated
@isaacbmiller
isaacbmiller force-pushed the isaac/react-v2-pr1-history-raw-dicts branch from df0f0c3 to e22f99f Compare May 20, 2026 03:17
@isaacbmiller
isaacbmiller force-pushed the isaac/react-v2-pr2-history-formatting branch from ee12841 to 6cb45e0 Compare May 20, 2026 03:20
Comment thread dspy/adapters/base.py Outdated
@isaacbmiller
isaacbmiller force-pushed the isaac/react-v2-pr1-history-raw-dicts branch from e22f99f to 5d3fbcf Compare May 22, 2026 20:24
@isaacbmiller
isaacbmiller force-pushed the isaac/react-v2-pr2-history-formatting branch from 6cb45e0 to 34b573e Compare May 22, 2026 20:24
@isaacbmiller isaacbmiller changed the title feat(adapter): format typed History events feat(adapter): render typed history frames May 22, 2026
Comment on lines +159 to +171
sections = []
if signature_outputs:
sections.append(
adapter.format_assistant_message_content(
signature,
signature_outputs,
missing_field_message="Not supplied for this conversation history message. ",
).strip()
)
for key, value in unknown_outputs.items():
sections.append(f"[[ ## {key} ## ]]\n{self._format_observation_content(value)}")
sections.append("[[ ## completed ## ]]")
return "\n\n".join(section for section in sections if section)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Double [[ ## completed ## ]] marker in mixed-output fallback

When outputs contains both signature-recognized fields (signature_outputs) and unrecognized fields (unknown_outputs), the fallback branch calls format_assistant_message_content — which itself appends [[ ## completed ## ]] — strips it, then appends the unknown-field sections and another [[ ## completed ## ]]. The resulting assistant history turn carries two end-of-turn sentinels, which can mislead the LM about the expected format or cause the DSPy response parser to stop early on any future response that contains a first completed marker before all fields.

The fast path (if signature_outputs and not unknown_outputs) is clean; the fix is to also strip the trailing [[ ## completed ## ]] from the format_assistant_message_content output before joining, so the final explicit sections.append("[[ ## completed ## ]]") is the only one.

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