Skip to content

[Chat] Fix tool call positioning in conversation timeline#11115

Merged
wanglam merged 3 commits intoopensearch-project:mainfrom
wanglam:fix-chat-tool-call-position
Jan 8, 2026
Merged

[Chat] Fix tool call positioning in conversation timeline#11115
wanglam merged 3 commits intoopensearch-project:mainfrom
wanglam:fix-chat-tool-call-position

Conversation

@wanglam
Copy link
Collaborator

@wanglam wanglam commented Dec 30, 2025

Description

This PR improves the reliability of tool call placement in the chat conversation timeline by implementing a multi-strategy approach for determining where tool calls should be attached. Previously, tool calls could be misplaced in the timeline when users sent new messages after an assistant's response, leading to incorrect conversation flow and potential confusion.

The fix introduces three strategies for tool call placement: (1) using an explicit parent message ID when provided by the backend, (2) analyzing timeline positions to determine if the last assistant message is still the current conversation turn, and (3) creating a placeholder assistant message when the LLM responds with only tool calls and no text message. This ensures tool calls are always correctly associated with their corresponding assistant responses, maintaining proper chronological ordering in the chat interface. Comprehensive unit tests have been added to validate all three placement strategies.

Screenshots

  • Before
image
  • After
image

Changelog

  • fix: [Chat] Fix tool call positioning in conversation timeline

Check List

  • All tests pass
    • yarn test:jest
    • yarn test:jest_integration
  • New functionality includes testing.
  • New functionality has been documented.
  • Update CHANGELOG.md
  • Commits are signed per the DCO using --signoff

Summary by CodeRabbit

  • Bug Fixes
    • Fixed tool call positioning in chat conversations to display at the correct location in the timeline. Tool calls are now properly positioned based on conversation context, with improved handling when explicit parent message information is not provided.

✏️ Tip: You can customize this high-level summary in your review settings.

Signed-off-by: Lin Wang <wonglam@amazon.com>
@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

This pull request fixes tool call positioning in the conversation timeline by reworking the placement logic in the chat event handler to intelligently attach tool calls to existing messages or create new placeholder messages, with corresponding test cases validating both scenarios.

Changes

Cohort / File(s) Summary
Changelog
changelogs/fragments/11115.yml
New changelog fragment documenting the fix for tool call positioning in conversation timeline (PR #11115)
Chat Event Handler Service
src/plugins/chat/public/services/chat_event_handler.ts
Reworked handleToolCallStart logic from simple fallback to multi-strategy approach: attaches tool calls to existing assistant messages when they precede user messages, or creates new fake assistant messages as placeholders when needed
Chat Event Handler Tests
src/plugins/chat/public/services/chat_event_handler.test.ts
Added two test cases covering tool call attachment without parentMessageId: attaching to existing last assistant message and creating new fake assistant message when timeline order requires it

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

bug

Suggested reviewers

  • kavilla
  • ruanyl
  • AMoo-Miki
  • joshuarrrr
  • ashwin-pc
  • SuZhou-Joe
  • virajsanghvi
  • zhongnansu
  • bandinib-amzn
  • paulstn
  • joshuali925

Poem

🐰 Hops through the timeline with glee,
Tool calls now placed where they should be,
No more positioning in disarray,
Fake messages save the chat today!
A rabbit's fix makes conversation bright,
Sequential wisdom burning bright!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: fixing tool call positioning in the chat conversation timeline.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required sections with clear explanations of the changes, visual before/after screenshots, and a proper changelog entry.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.37%. Comparing base (f5266df) to head (96802e2).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #11115   +/-   ##
=======================================
  Coverage   60.36%   60.37%           
=======================================
  Files        4591     4591           
  Lines      126396   126410   +14     
  Branches    21183    21184    +1     
=======================================
+ Hits        76295    76314   +19     
+ Misses      44687    44682    -5     
  Partials     5414     5414           
Flag Coverage Δ
Linux_1 26.36% <ø> (ø)
Linux_2 38.27% <ø> (ø)
Linux_3 39.81% <100.00%> (+0.01%) ⬆️
Linux_4 33.31% <ø> (ø)
Windows_1 26.37% <ø> (ø)
Windows_2 38.25% <ø> (ø)
Windows_3 39.83% <100.00%> (+0.01%) ⬆️
Windows_4 33.31% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/plugins/chat/public/services/chat_event_handler.ts (1)

275-289: LGTM! Strategy 3 correctly creates a placeholder when needed.

The fake message creation handles the case where the LLM returns only tool calls without a text message, ensuring proper timeline structure.

Minor consistency suggestion: Consider using Date.now() instead of new Date().getTime() for consistency with other parts of the file (e.g., line 233).

🔎 Optional consistency improvement
-    const fakeAssistantMessageId = `fake-assistant-message-` + new Date().getTime();
+    const fakeAssistantMessageId = `fake-assistant-message-${Date.now()}`;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed7c506 and 5809bad.

📒 Files selected for processing (3)
  • changelogs/fragments/11115.yml
  • src/plugins/chat/public/services/chat_event_handler.test.ts
  • src/plugins/chat/public/services/chat_event_handler.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/plugins/chat/public/services/chat_event_handler.ts (1)
src/core/public/chat/types.ts (1)
  • AssistantMessage (54-58)
🪛 YAMLlint (1.37.1)
changelogs/fragments/11115.yml

[error] 2-2: syntax error: expected , but found ''

(syntax)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
  • GitHub Check: Run cypress tests (osd:ciGroup12)
  • GitHub Check: Run cypress tests (osd:ciGroup10Explore)
🔇 Additional comments (5)
src/plugins/chat/public/services/chat_event_handler.test.ts (2)

365-403: LGTM! Comprehensive test for Strategy 2 (timeline-based placement).

The test correctly validates that when the last assistant message appears after the last user message in the timeline, the tool call is attached to the existing assistant message rather than creating a new one.


405-447: LGTM! Comprehensive test for Strategy 3 (fake message creation).

The test correctly validates that when a user message appears after the last assistant message, a new placeholder assistant message is created to hold the tool call. This ensures proper conversation flow when users send new messages after an assistant response.

src/plugins/chat/public/services/chat_event_handler.ts (3)

213-224: LGTM! Clear documentation of the multi-strategy approach.

The comments effectively explain the three placement strategies and their rationale, making the code maintainable and easy to understand.


249-254: LGTM! Strategy 1 implementation is correct.

Using the explicitly provided parentMessageId when available is the most reliable approach and avoids any ambiguity in tool call placement.


256-273: LGTM! Strategy 2 correctly determines tool call placement based on timeline positions.

The logic properly handles edge cases:

  • When lastTextMessageStartId is null, it falls through to Strategy 3
  • When there are no user messages (lastUserMessageIndex === -1), the assistant message takes precedence
  • The use of findLastIndex ensures the most recent messages are compared

Comment on lines +1 to +2
fix:
- [Chat] Fix tool call positioning in conversation timeline ([#11115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11115)) No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix YAML syntax error by quoting the changelog entry.

The URL contains special characters (brackets and parentheses) that must be quoted in YAML to avoid parsing errors.

🔎 Proposed fix
 fix:
-- [Chat] Fix tool call positioning in conversation timeline ([#11115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11115))
+- "[Chat] Fix tool call positioning in conversation timeline ([#11115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11115))"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fix:
- [Chat] Fix tool call positioning in conversation timeline ([#11115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11115))
fix:
- "[Chat] Fix tool call positioning in conversation timeline ([#11115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/11115))"
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 2-2: syntax error: expected , but found ''

(syntax)

🤖 Prompt for AI Agents
In changelogs/fragments/11115.yml around lines 1 to 2, the changelog entry
contains unquoted special characters (brackets and parentheses) in the URL which
breaks YAML parsing; wrap the entire list item string in quotes (single or
double) so the dash is followed by a quoted string that includes the bracketed
PR link, e.g. change - [Chat] Fix tool... ([#11115](...)) to a single quoted
line; ensure matching quotes and no unescaped internal quotes.

const targetMessageId = parentMessageId || this.lastTextMessageStartId;
// Strategy 1: Use explicitly provided parent message ID
// This is the most reliable approach when the backend provides it
if (parentMessageId) {
Copy link
Member

Choose a reason for hiding this comment

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

Under what situation the parentMessageId will not available?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This parentMessageId comes from the chat agent event stream and depends on the chat agent implementation. Typically, it has a valid value when the agent responds with a message that initiates tool calls. All tool calls from the same response share the same parentMessageId.

Previously, the implementation would fall back to the latest chat agent message when parentMessageId wasn't provided. In this PR, I added logic to check if there are any user input messages after the latest chat agent message. If so, it creates a virtual chat agent message (frontend-only) to properly group these tool calls.

}

// Strategy 3: Create a new assistant message placeholder
// This handles the case where the LLM responds with tool calls but without any text message.
Copy link
Member

Choose a reason for hiding this comment

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

is this an abnormal case because LLM didn't follow our prompt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This depends on the chat agent implementation. From my perspective, if the user asks the chat agent to do something straightforward, having no text message before the tool calling section makes sense to me.

@Hailong-am
Copy link
Collaborator

can you paste a screenshot of before and after?

@wanglam
Copy link
Collaborator Author

wanglam commented Jan 6, 2026

Hi @Hailong-am , I've added the screenshots of before and after. Feel free to help me review it. Thank you.

@wanglam wanglam merged commit 18c8f63 into opensearch-project:main Jan 8, 2026
108 of 110 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants