Skip to content

feat: add a chat group mode conversation#1092

Open
clementb49 wants to merge 1 commit intomasterfrom
chatGroup
Open

feat: add a chat group mode conversation#1092
clementb49 wants to merge 1 commit intomasterfrom
chatGroup

Conversation

@clementb49
Copy link
Collaborator

@clementb49 clementb49 commented Mar 14, 2026

Summary by CodeRabbit

  • New Features
    • Group conversations now properly track and display participant context throughout the conversation
    • Message history is presented from each participant's perspective in group conversations
    • Prevents duplicate prompt content for secondary participants in group conversations, ensuring cleaner continuation from history

…versations

- Restructure get_messages() for group chat: other participants' responses
  now appear as USER turns with [name]: attribution instead of prefixing
  ASSISTANT content. This prevents models from self-prefixing their own
  responses or generating multiple personas in one block.
- Add _current_group_participant_id PrivateAttr to Conversation so
  get_messages() knows whose POV to build history from.
- Fix empty 'User:' lines appearing between debate rounds: is_group_followup
  now also skips user request display when request content is empty.
- Fix duplicate user question: only position 0 in round 0 receives the
  original prompt; all others get an empty sentinel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@clementb49 clementb49 linked an issue Mar 14, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

Walkthrough

This pull request implements perspective-based message history assembly for group conversations. It adds tracking of the current group participant, sets profile name mappings for attribution, gates initial request content exclusively to the first participant, and reworks how messages are presented so each participant sees their own responses as assistant turns and others' responses as user turns with proper attribution.

Changes

Cohort / File(s) Summary
Model Updates
basilisk/conversation/conversation_model.py
Added private attribute _current_group_participant_id to track the active group participant and updated related comment for profile name map attribution context.
Group Conversation Presenter
basilisk/presenters/group_conversation_presenter.py
Sets conversation profile name map and tracks current participant via _current_group_participant_id. Gates request content to first participant only by sending empty string for subsequent participants in first round.
Message Engine Rework
basilisk/provider_engine/base_engine.py
Reworks get_messages to assemble group conversation history from each participant's perspective: own prior responses appear as ASSISTANT turns, others' responses become USER turns with [name]: attribution. Non-group blocks retain original logic with updated stop index behavior.
View Layer
basilisk/views/history_msg_text_ctrl.py
Updated group-followup determination logic to check group_id existence and evaluate request content emptiness alongside group_position > 0 for proper display behavior.
Test Coverage
tests/presenters/test_group_conversation_presenter.py, tests/test_group_message_block.py
Added tests verifying empty request content for non-first participants in debate mode and private attribute behavior for _current_group_participant_id (default value and JSON serialization exclusion).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add a chat group mode conversation' directly summarizes the main change—adding group chat mode functionality with support for multiple participants, tracking current participant state, POV-based message history, and related UI updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Important

Merge conflicts detected

  • Resolve merge conflict in branch chatGroup
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chatGroup
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting to disable sequence diagrams in the walkthrough.

Copy link
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@basilisk/provider_engine/base_engine.py`:
- Around line 155-178: When rebuilding group history in the block-handling
branch (the code using current_pid, block.group_id, pid_str, name_map and
creating Message with MessageRoleEnum.USER), also replay the original request
for non-own participants so later participants receive the opener turn;
specifically, when handling "another participant's response" append the
block.request as a user turn before or together with the attributed response
using prepare_message_request, and change the guard so it includes requests that
have attachments even if request.content == "" (e.g., check block.request exists
and either content or attachments are present). Apply the same fix to the
analogous code path around the second region referenced (the block range also at
~188-191) so both group-history reconstruction sites include attachment-only or
empty-content opener requests.

In `@basilisk/views/history_msg_text_ctrl.py`:
- Around line 234-239: The condition computing is_group_followup should guard
new_block.group_position against None before comparing to 0; update the
expression that references new_block.group_position in the history message
controller (the new_block variable in basilisk.views.history_msg_text_ctrl) to
first check new_block.group_position is not None (e.g., use
"new_block.group_position is not None and new_block.group_position > 0") so that
when new_block.group_id is present but group_position is None you won't get a
TypeError; keep the existing checks for new_block.group_id and not
new_block.request.content as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fa7927eb-3e71-475a-b33b-56631606e413

📥 Commits

Reviewing files that changed from the base of the PR and between 1860e0f and acf4e51.

📒 Files selected for processing (6)
  • basilisk/conversation/conversation_model.py
  • basilisk/presenters/group_conversation_presenter.py
  • basilisk/provider_engine/base_engine.py
  • basilisk/views/history_msg_text_ctrl.py
  • tests/presenters/test_group_conversation_presenter.py
  • tests/test_group_message_block.py

Comment on lines +155 to +178
if current_pid is not None and block.group_id is not None:
# Group block: rebuild history from this participant's POV.
pid_str = str(block.profile_id) if block.profile_id else None
if pid_str == current_pid:
# Own previous response: include request (if non-empty)
# then own response as ASSISTANT.
if block.request.content:
messages.append(
self.prepare_message_request(block.request)
)
messages.append(
self.prepare_message_response(block.response)
)

response = Message(
role=MessageRoleEnum.ASSISTANT,
content=f"[{profile_name}]: {response.content}",
else:
# Another participant's response: frame as USER turn so
# the model doesn't imitate the attribution format.
other_name = (
name_map.get(pid_str, "Other") if pid_str else "Other"
)
attributed = Message(
role=MessageRoleEnum.USER,
content=f"[{other_name}]: {block.response.content}",
)
messages.append(self.prepare_message_response(response))
messages.append(self.prepare_message_request(new_block.request))
messages.append(self.prepare_message_request(attributed))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replay the opener request when rebuilding group history.

Once participant 2+ is sent request_content="", this method becomes the
only place they can still receive the original user turn. Right now non-own
group blocks contribute only block.response, and the if ...content
guards also drop attachment-only requests. In normal mode that leaves later
participants without the user's prompt/files.

🛠️ Proposed fix
+		def has_user_input(message: Message) -> bool:
+			return bool(message.content or message.attachments)
+
 		for i, block in enumerate(conversation.messages):
 			if stop_block_index is not None and i >= stop_block_index:
 				break
 			if not block.response:
 				continue
 			if current_pid is not None and block.group_id is not None:
 				# Group block: rebuild history from this participant's POV.
 				pid_str = str(block.profile_id) if block.profile_id else None
 				if pid_str == current_pid:
 					# Own previous response: include request (if non-empty)
 					# then own response as ASSISTANT.
-					if block.request.content:
+					if has_user_input(block.request):
 						messages.append(
 							self.prepare_message_request(block.request)
 						)
 					messages.append(
 						self.prepare_message_response(block.response)
 					)
 				else:
+					if has_user_input(block.request):
+						messages.append(
+							self.prepare_message_request(block.request)
+						)
 					# Another participant's response: frame as USER turn so
 					# the model doesn't imitate the attribution format.
 					other_name = (
 						name_map.get(pid_str, "Other") if pid_str else "Other"
 					)
 					attributed = Message(
 						role=MessageRoleEnum.USER,
 						content=f"[{other_name}]: {block.response.content}",
 					)
 					messages.append(self.prepare_message_request(attributed))
 		# Append the new block's request only when it carries content.
 		# For group follow-ups the last USER turn is already an attribution.
-		if new_block.request.content:
+		if has_user_input(new_block.request):
 			messages.append(self.prepare_message_request(new_block.request))

Also applies to: 188-191

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@basilisk/provider_engine/base_engine.py` around lines 155 - 178, When
rebuilding group history in the block-handling branch (the code using
current_pid, block.group_id, pid_str, name_map and creating Message with
MessageRoleEnum.USER), also replay the original request for non-own participants
so later participants receive the opener turn; specifically, when handling
"another participant's response" append the block.request as a user turn before
or together with the attributed response using prepare_message_request, and
change the guard so it includes requests that have attachments even if
request.content == "" (e.g., check block.request exists and either content or
attachments are present). Apply the same fix to the analogous code path around
the second region referenced (the block range also at ~188-191) so both
group-history reconstruction sites include attachment-only or empty-content
opener requests.

Comment on lines +234 to 239
# Skip user request for group follow-up blocks:
# - any block with group_position > 0 (not the chain opener), or
# - position-0 blocks in subsequent rounds (empty sentinel request).
is_group_followup = new_block.group_id is not None and (
new_block.group_position > 0 or not new_block.request.content
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
group_position = None
try:
	group_position > 0
except Exception as exc:
	print(type(exc).__name__, str(exc))
PY

Repository: SigmaNight/basiliskLLM

Length of output: 135


🏁 Script executed:

# Check the MessageBlock.group_position type definition
grep -n "group_position" basilisk/conversation/conversation_model.py | head -20

Repository: SigmaNight/basiliskLLM

Length of output: 119


🏁 Script executed:

# Read the exact code at lines 234-239 in history_msg_text_ctrl.py
sed -n '234,239p' basilisk/views/history_msg_text_ctrl.py

Repository: SigmaNight/basiliskLLM

Length of output: 381


🏁 Script executed:

# Get broader context around lines 234-239 to understand the flow
sed -n '230,245p' basilisk/views/history_msg_text_ctrl.py

Repository: SigmaNight/basiliskLLM

Length of output: 778


Guard group_position against None before comparison.

Line 238 performs new_block.group_position > 0 without checking if group_position is None. Since MessageBlock.group_position is typed as int | None, this comparison raises TypeError when group_id is present but group_position is None. The first check (new_block.group_id is not None) does not guarantee group_position is also non-None.

Proposed fix
-		is_group_followup = new_block.group_id is not None and (
-			new_block.group_position > 0 or not new_block.request.content
-		)
+		group_position = new_block.group_position
+		is_group_followup = new_block.group_id is not None and (
+			(
+				group_position is not None
+				and group_position > 0
+			)
+			or not new_block.request.content
+		)
📝 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
# Skip user request for group follow-up blocks:
# - any block with group_position > 0 (not the chain opener), or
# - position-0 blocks in subsequent rounds (empty sentinel request).
is_group_followup = new_block.group_id is not None and (
new_block.group_position > 0 or not new_block.request.content
)
# Skip user request for group follow-up blocks:
# - any block with group_position > 0 (not the chain opener), or
# - position-0 blocks in subsequent rounds (empty sentinel request).
group_position = new_block.group_position
is_group_followup = new_block.group_id is not None and (
(
group_position is not None
and group_position > 0
)
or not new_block.request.content
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@basilisk/views/history_msg_text_ctrl.py` around lines 234 - 239, The
condition computing is_group_followup should guard new_block.group_position
against None before comparing to 0; update the expression that references
new_block.group_position in the history message controller (the new_block
variable in basilisk.views.history_msg_text_ctrl) to first check
new_block.group_position is not None (e.g., use "new_block.group_position is not
None and new_block.group_position > 0") so that when new_block.group_id is
present but group_position is None you won't get a TypeError; keep the existing
checks for new_block.group_id and not new_block.request.content as-is.

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.

A suggestion...

1 participant