Skip to content

Fix: Ensure step messages represent input only#884

Merged
sixlive merged 2 commits intoprism-php:mainfrom
vinitkadam03:fix/text-step-messages
Feb 1, 2026
Merged

Fix: Ensure step messages represent input only#884
sixlive merged 2 commits intoprism-php:mainfrom
vinitkadam03:fix/text-step-messages

Conversation

@vinitkadam03
Copy link
Copy Markdown
Contributor

@vinitkadam03 vinitkadam03 commented Jan 31, 2026

Description

Updated text handlers and stream handlers across all providers where step messages contained both input and output messages.

In text handlers: The assistant message (response) was being added to the request before addStep() was called, which caused each step's messages array to include the assistant's response alongside the input messages.

In stream handlers: The assistant and tool result messages were being added to the request before the StepFinishEvent was dispatched, which caused the same issue where each step's messages would include the output alongside the input.

The Problem

In multi-step tool call scenarios:

  • Step N's messages was including the assistant message (output) that was generated during that step
  • Ideally step->messages should represent only the input to that step
  • The output is already captured in other step properties (text, toolCalls, toolResults, etc.)

Note: This bug can cause issues with observability/telemetry systems. If you're storing the steps returned by asText() or collected via streaming for logging, tracing, or analytics, the incorrect message structure would show the assistant's output duplicated - once in step->messages and again in step->text/step->toolCalls. This makes it difficult to accurately reconstruct the conversation flow and could lead to confusing traces. Additionally, it becomes tricky to determine what input was actually sent to the LLM at each step, as users have to cross-reference other attributes like text, toolCalls, toolResults, etc. to distinguish between input and output within the messages array.

The Fix

Text Handlers: Moved the assistant message addition to after addStep() in all provider text handlers.

Stream Handlers: Moved the assistant and tool result message additions to after yielding StepFinishEvent in all provider stream handlers.

Affected providers:

  • Anthropic, OpenAI, OpenRouter, DeepSeek, Gemini, Groq, Mistral, Ollama, XAI

Now in multi-step scenarios:

  • Step 1: messages = [user message] (input only)
  • Step 2: messages = [user message, assistant message from step 1, tool results] (input only)
  • And so on...

Tests Updated

Added assertions across all provider test files to verify:

  • The assistant message from step N correctly appears in step N+1's input messages
  • Each step's messages only contain the input to that step

Breaking Changes

Text Handlers

step->messages will no longer include the assistant message generated during that step - only the input messages to that step. If you were accessing the assistant message via $response->steps[N]->messages, you should now use $response->steps[N]->text or $response->steps[N]->toolCalls instead.

No breaking change for $response->messages->last() - it still returns the final assistant message for conversation continuation.

Stream Handlers

When the StepFinishEvent is dispatched after tool results are executed, the request messages will no longer contain the assistant and tool result messages at that point. These messages are now added to the request after the StepFinishEvent is yielded. If you were accessing $request->messages() in a StepFinishEvent listener expecting to see the assistant response and tool results, they will no longer be present at that time.

@vinitkadam03 vinitkadam03 changed the title fix: move assistant message and tool result message addition after ad… Fix: Ensure step messages represent input only Jan 31, 2026
@vinitkadam03 vinitkadam03 marked this pull request as draft January 31, 2026 21:27
@vinitkadam03 vinitkadam03 force-pushed the fix/text-step-messages branch from 277c64f to 041d7de Compare January 31, 2026 21:38
@vinitkadam03 vinitkadam03 marked this pull request as ready for review January 31, 2026 21:43
@vinitkadam03
Copy link
Copy Markdown
Contributor Author

vinitkadam03 commented Feb 1, 2026

@sixlive
What should $response->messages contain?

Currently after this change:

  • step->messages = input messages only (the change in this PR)
  • $response->messages = final step's input messages + final assistant message

I'm questioning whether $response->messages should also be input-only to match step->messages.

The concern: We're duplicating state. The assistant's response is already available via:

  • $response->text
  • $response->toolCalls
  • $response->additionalContent

Having it also in $response->messages->last() creates inconsistency:

  • step->messages = input only
  • $response->messages = input + output

Two options:

  1. Keep assistant message in $response->messages (current implementation)

    • Pro: Useful for conversation continuation - users can pass $response->messages directly
    • Con: Duplicates state, inconsistent with step->messages
  2. Make $response->messages input-only (matching step->messages)

    • Pro: Consistent, no duplication, clear that "messages" = input and we can optionally add outputMessages as well which will contain assistant message & tool result messages
    • Con: Users need to now send messages + outputMessages in subsequent requests to continue conversation

What's the intended design for messages?

@vinitkadam03
Copy link
Copy Markdown
Contributor Author

vinitkadam03 commented Feb 1, 2026

I just checked the stream handler, and when the finish reason is stop, the assistant message is not added to the request before dispatching the step-finish and stream-end events.

I think the same behavior should apply to the text request state as well—the messages should represent only the input messages sent to the LLM to generate the output for that step.

I also checked what happens when the finish reason is tool_calls in the stream handlers. In that case, the assistant message is added before dispatching the step-finish event.
This feels a bit inconsistent. Ideally, the step-finish event should be dispatched before adding the assistant message and tool results to the request in stream handlers as well.

@sixlive
Copy link
Copy Markdown
Contributor

sixlive commented Feb 1, 2026

The inconsistency is intentional - they serve different purposes:

  • step->messages represents the input to that step (useful for observability/debugging)
  • $response->messages represents the conversation state for continuation (useful for follow-up requests)

This way, users can pass $response->messages directly to continue a conversation without manually reconstructing the assistant message.

@sixlive sixlive merged commit aa0f29d into prism-php:main Feb 1, 2026
14 checks passed
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.

2 participants