Skip to content

fix(slack): NestBot deferred replies format without a second LLM call#4320

Closed
rajnisk wants to merge 1 commit intoOWASP:feature/nestbot-ai-assistantfrom
rajnisk:feature/nestbot-ai-reply-handling
Closed

fix(slack): NestBot deferred replies format without a second LLM call#4320
rajnisk wants to merge 1 commit intoOWASP:feature/nestbot-ai-assistantfrom
rajnisk:feature/nestbot-ai-reply-handling

Conversation

@rajnisk
Copy link
Copy Markdown

@rajnisk rajnisk commented Mar 19, 2026

Proposed change

Resolves #3693

This PR implements reply handling fixes for NestBot’s delayed auto-reply path (generate_ai_reply_if_unanswered): how answers are formatted and posted to Slack after the question-detector pipeline, without overlapping the Gemini provider, multi-agent orchestration, or interaction-UX PRs.

What changed

  • Bug fix: generate_ai_reply_if_unanswered no longer calls get_blocks(ai_response_text, …), which incorrectly ran process_ai_query again on the model’s answer. It now uses format_blocks(), which only formats pre-rendered text.
  • format_blocks() splits long answers into multiple Slack section blocks so mrkdwn stays under Slack’s per-block limit (~3001 characters; we target 3000).
  • chat.postMessage text: fallback/notification text is derived from format_ai_response_for_slack, truncated with an ellipsis when needed, and aligned with block formatting (not raw model output only).
  • format_ai_response_for_slack: stops calling format_links_for_slack directly; link conversion happens once via markdown() / format_links_for_slack per block (avoids double processing).
  • Guards: reject bare YES / NO outputs from the pipeline in process_ai_query; treat empty/whitespace-only formatted output as error blocks instead of posting an empty block list.
  • Tests: updated handler and auto-reply tests; added coverage for splitting, links, empty input, and YES/NO rejection.

Files touched

  • backend/apps/slack/common/handlers/ai.py
  • backend/apps/slack/services/message_auto_reply.py
  • backend/apps/slack/utils.py
  • backend/tests/apps/slack/common/handlers/ai_test.py
  • backend/tests/apps/slack/services/message_auto_reply_test.py

Checklist

  • [*] Required: I followed the contributing workflow
  • [*] Required: I verified that my code works as intended and resolves the issue as described
  • [*] Required: I ran make check-test locally: all warnings addressed, tests passed
  • [*] I used AI for code, documentation, tests, or communication related to this PR

Add format_blocks() for pre-rendered text, chunk mrkdwn under Slack limits, align chat.postMessage fallback text with formatting, dedupe link conversion via markdown(), and guard empty/YES/NO outputs. Update tests.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • AI responses exceeding character limits are now split into multiple blocks for improved readability.
  • Bug Fixes

    • Enhanced fallback text rendering in Slack messages with better formatting and truncation handling.
    • Improved edge case handling for empty or single-word AI responses.

Walkthrough

This PR refactors Slack AI response formatting by introducing a format_blocks() function that splits oversized AI responses into multiple valid Slack blocks with character limits. It updates process_ai_query() to filter out bare YES/NO responses, modifies format_ai_response_for_slack() to handle nullable input, and adjusts callers and tests throughout the Slack integration layer.

Changes

Cohort / File(s) Summary
Core AI Handler
backend/apps/slack/common/handlers/ai.py
Added MAX_BLOCK_TEXT_LENGTH constant and format_blocks() helper to split AI responses into multiple Slack blocks while respecting character limits. Updated get_blocks() to use format_blocks() instead of single markdown() block. Modified process_ai_query() to filter YES/NO responses, return None on exceptions, and add logging metadata.
Message Auto-Reply Service
backend/apps/slack/services/message_auto_reply.py
Replaced get_blocks() call with format_blocks(). Added plain-text fallback with _FALLBACK_TEXT_MAX limit and whitespace handling, passing both blocks and text parameters to chat_postMessage().
Utilities
backend/apps/slack/utils.py
Updated format_ai_response_for_slack() signature to accept str | None and return empty string for falsy inputs. Removed format_links_for_slack() call, delegating Markdown link conversion to per-block formatting elsewhere.
Test Coverage
backend/tests/apps/slack/common/handlers/ai_test.py, backend/tests/apps/slack/services/message_auto_reply_test.py
Updated test mocks to patch format_blocks instead of get_blocks. Added new test cases for format_blocks() multi-block splitting, link rendering, and process_ai_query() YES/NO filtering. Updated test assertions for new function signatures and return types.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #2113 — Modifies the same Slack AI handler and message auto-reply files with related block formatting and response handling changes
  • #3939 — Updates process_ai_query() and imports in the same AI handler file with complementary logic modifications
  • #2407 — Adds format_links_for_slack() utility that coordinates with this PR's refactoring of link conversion responsibility to per-block formatting

Suggested labels

nestbot, backend, backend-tests, Nestbot-ai-assistant

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR addresses reply formatting in the deferred auto-reply path but does not implement reaction management fixes, RAG improvements, or greeting routing corrections listed in issue #3693. Verify whether this PR is intended as a partial fix scoped only to reply formatting, or if additional changes are needed to fully resolve issue #3693.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix(slack): NestBot deferred replies format without a second LLM call' accurately describes the main change—eliminating a redundant LLM call in deferred reply handling and fixing reply formatting.
Description check ✅ Passed The PR description comprehensively explains the changes, rationale, and files modified, clearly relating to the changeset and providing sufficient context.
Out of Scope Changes check ✅ Passed All code changes are directly related to formatting replies and eliminating the duplicate LLM call in the deferred auto-reply path, with no unrelated modifications detected.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 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.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
backend/tests/apps/slack/common/handlers/ai_test.py (1)

196-205: Consider using the constant for consistency.

The assertion len(block_text) <= 3001 hardcodes the Slack limit while MAX_BLOCK_TEXT_LENGTH (3000) is the production target. For clarity, consider either using <= MAX_BLOCK_TEXT_LENGTH to match the production guarantee, or adding a brief comment explaining that 3001 is Slack's actual limit while 3000 is the safety margin.

♻️ Suggested change
         for block in blocks:
             block_text = block["text"]["text"]
-            assert len(block_text) <= 3001  # Slack mrkdwn section limit
+            assert len(block_text) <= MAX_BLOCK_TEXT_LENGTH  # production safety limit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/common/handlers/ai_test.py` around lines 196 - 205,
The test test_format_blocks_splits_long_text uses a hardcoded 3001 in the
assertion; update it to use the constant MAX_BLOCK_TEXT_LENGTH (or assert <=
MAX_BLOCK_TEXT_LENGTH + 1 with a clarifying comment if you intend to document
Slack's absolute limit) so the test aligns with the production target and
references the same limit used by format_blocks and the MAX_BLOCK_TEXT_LENGTH
constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/tests/apps/slack/common/handlers/ai_test.py`:
- Around line 196-205: The test test_format_blocks_splits_long_text uses a
hardcoded 3001 in the assertion; update it to use the constant
MAX_BLOCK_TEXT_LENGTH (or assert <= MAX_BLOCK_TEXT_LENGTH + 1 with a clarifying
comment if you intend to document Slack's absolute limit) so the test aligns
with the production target and references the same limit used by format_blocks
and the MAX_BLOCK_TEXT_LENGTH constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a83bbe0b-ceff-40e6-a584-bf03dfbd384e

📥 Commits

Reviewing files that changed from the base of the PR and between d6b2e85 and 712fb7f.

📒 Files selected for processing (5)
  • backend/apps/slack/common/handlers/ai.py
  • backend/apps/slack/services/message_auto_reply.py
  • backend/apps/slack/utils.py
  • backend/tests/apps/slack/common/handlers/ai_test.py
  • backend/tests/apps/slack/services/message_auto_reply_test.py

@rajnisk rajnisk closed this Mar 19, 2026
@rajnisk
Copy link
Copy Markdown
Author

rajnisk commented Mar 19, 2026

Superseded: equivalent changes (deferred reply formatting without re-running the model, Slack block limits, fallback text, link handling, YES/NO + empty guards, tests) are merged into the #4319

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.

1 participant