camel icon indicating copy to clipboard operation
camel copied to clipboard

feat: blockkit integration into send_slack_message method for feature request #2839

Open waleedalzarooni opened this issue 5 months ago • 11 comments

Description

I have added the functionality of sending block kit objects in Slack, including helper functions to quickly create common objects such as menus and buttons, (waiting on confirmation that approach is correct before example file is created)

Checklist

Go over all the following points, and put an x in all the boxes that apply.

  • [ X] I have read the CONTRIBUTION guide (required)
  • [X ] I have linked this PR to an issue using the Development section on the right sidebar or by adding Fixes #issue-number in the PR description (required)
  • [X ] I have checked if any dependencies need to be added or updated in pyproject.toml and uv lock
  • [X ] I have updated the tests accordingly (required for a bug fix or a new feature)
  • [ X] I have updated the documentation if needed:
  • [x] I have added examples if this is a new feature

If you are unsure about any of these, don't hesitate to ask. We are here to help!

waleedalzarooni avatar Jul 18 '25 13:07 waleedalzarooni

[!IMPORTANT]

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch slack-blockkit-integration

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

coderabbitai[bot] avatar Jul 18 '25 13:07 coderabbitai[bot]

A couple of extra comments

To save time and not over lengthen the slack_toolkit file, I only created element helper functions for the most commonly used elements

In the latest commit I included more through error handling, let me know if I went overboard and should cut things down

waleedalzarooni avatar Jul 23 '25 16:07 waleedalzarooni

@fengju0213, I see your point that makes a lot of sense I can see how the agent could be distracted by the volume of functions. I will have a shot at this Fri-Sun and let you know how my progress goes or whether I have any additional questions!

waleedalzarooni avatar Aug 21 '25 16:08 waleedalzarooni

Hey @fengju0213, I had a think about some approaches to unify the methods, let me know what you think about these two options!

    def create_a_blockkit(
    self,
    block_type: str,
    **kwargs
) -> dict:
    r"""Creates various Slack Block Kit elements based on the specified type.

    This unified method creates different Block Kit elements by specifying the
    block_type and providing the appropriate parameters for each type.

    Args:
        block_type (str): The type of Block Kit element to create. Valid types:
            - 'button': Creates a button element
            - 'select_menu': Creates a static select menu
            - 'plain_text_input': Creates a plain text input field
            - 'date_picker': Creates a date picker
            - 'image': Creates an image block
            - 'option': Creates an option for select menus
            - 'confirm_object': Creates a confirmation dialog object
            - 'option_group': Creates an option group for select menus
            - 'text_object': Creates a text object (plain_text or mrkdwn)
            - 'slack_file_object': Creates a Slack file object
        **kwargs: Parameters specific to the block_type. See individual method
            documentation for required and optional parameters.

    Returns:
        dict: A dictionary representing the specified Slack Block Kit element.

    Raises:
        ValueError: If block_type is not supported or if required parameters
            are missing/invalid for the specified type.

    Examples:
        # Create a button
        button = create_a_blockkit(
            'button',
            text={"type": "plain_text", "text": "Click me"},
            action_id="button_1"
        )

        # Create a select menu
        select_menu = create_a_blockkit(
            'select_menu',
            placeholder={"type": "plain_text", "text": "Choose an option"},
            options=[
                {"text": {"type": "plain_text", "text": "Option 1"}, "value": "1"},
                {"text": {"type": "plain_text", "text": "Option 2"}, "value": "2"}
            ]
        )

        # Create a text input
        text_input = create_a_blockkit(
            'plain_text_input',
            action_id="input_1",
            placeholder={"type": "plain_text", "text": "Enter text here"}
        )
    """
    block_type = block_type.lower()
    
    # Map block types to their creation methods
    block_creators = {
        'button': self._create_button,
        'select_menu': self._create_select_menu,
        'plain_text_input': self._create_plain_text_input,
        'date_picker': self._create_date_picker,
        'image': self._create_image,
        'option': self._create_option,
        'confirm_object': self._create_confirm_object,
        'option_group': self._create_option_group,
        'text_object': self._create_text_object,
        'slack_file_object': self._create_slack_file_object,
    }
    
    if block_type not in block_creators:
        valid_types = ', '.join(block_creators.keys())
        raise ValueError(
            f"Unsupported block_type '{block_type}'. "
            f"Valid types are: {valid_types}"
        )
    
    # Call the appropriate internal method
    return block_creators[block_type](**kwargs)`

This approach still utilises the internal methods that I wrote so maybe not the agent led approach you prefer, alternatively this second approach will provide much more responsibility to the agent and may provide the docstring instructions strategy you're referring to

   `def create_blockkit(
    self,
    block_type: str,
    **kwargs
) -> dict:
    r"""Creates Slack Block Kit elements by providing the block type and parameters.

    This method allows you to create any Slack Block Kit element by specifying the
    block_type and providing the appropriate parameters. The method validates the
    structure and returns a properly formatted Block Kit dictionary.

    SUPPORTED BLOCK TYPES AND THEIR PARAMETERS:

    1. BUTTON ('button'):
       Required: text (dict with 'type' and 'text' keys)
       Optional: action_id, value, style ('primary' or 'danger'), url, confirm, accessibility_label
       Example: {"type": "button", "text": {"type": "plain_text", "text": "Click me"}, "action_id": "btn1"}

    2. SELECT MENU ('static_select'):
       Required: placeholder (dict with 'type' and 'text' keys)
       Optional: action_id, options (list), option_groups (list), initial_option, confirm, focus_on_load
       Example: {"type": "static_select", "placeholder": {"type": "plain_text", "text": "Choose..."}, "options": [...]}

    3. PLAIN TEXT INPUT ('plain_text_input'):
       Optional: action_id, multiline, placeholder, initial_value, min_length, max_length, dispatch_action_config, focus_on_load
       Example: {"type": "plain_text_input", "action_id": "input1", "multiline": true}

    4. DATE PICKER ('datepicker'):
       Optional: action_id, placeholder, initial_date (YYYY-MM-DD), confirm, focus_on_load
       Example: {"type": "datepicker", "action_id": "date1", "initial_date": "2024-01-15"}

    5. IMAGE ('image'):
       Required: alt_text (string)
       Optional: image_url, slack_file (dict with 'id' or 'url')
       Example: {"type": "image", "alt_text": "Description", "image_url": "https://..."}

    6. OPTION ('option'):
       Required: text (dict), value (string)
       Optional: description, url
       Example: {"text": {"type": "plain_text", "text": "Option 1"}, "value": "1"}

    7. CONFIRM OBJECT ('confirm'):
       Required: title, text, confirm, deny (all dict objects)
       Optional: style ('primary' or 'danger')
       Example: {"title": {"type": "plain_text", "text": "Title"}, "text": {...}, "confirm": {...}, "deny": {...}}

    8. OPTION GROUP ('option_group'):
       Required: label (dict), options (list of option objects)
       Example: {"label": {"type": "plain_text", "text": "Group 1"}, "options": [...]}

    9. TEXT OBJECT ('text'):
       Required: type ('plain_text' or 'mrkdwn'), text (string)
       Optional: emoji (for plain_text), verbatim (for mrkdwn)
       Example: {"type": "plain_text", "text": "Hello world", "emoji": true}

    10. SLACK FILE OBJECT ('file'):
        Required: Either 'id' or 'url' (string)
        Example: {"id": "F1234567890"} or {"url": "https://..."}

    TEXT OBJECT FORMAT:
    All text objects should be dictionaries with this structure:
    - For plain_text: {"type": "plain_text", "text": "your text here"}
    - For mrkdwn: {"type": "mrkdwn", "text": "your *markdown* text here"}

    VALIDATION RULES:
    - Text objects must have 'type' and 'text' keys
    - Character limits: button text (75), placeholder (150), action_id (255), etc.
    - Style values must be 'primary' or 'danger'
    - Date format must be YYYY-MM-DD
    - URLs must be valid strings
    - Lists (options, option_groups) have maximum limits

    Args:
        block_type (str): The type of Block Kit element to create. Must be one of:
            'button', 'static_select', 'plain_text_input', 'datepicker', 'image',
            'option', 'confirm', 'option_group', 'text', 'file'
        **kwargs: Parameters for the block as key-value pairs.

    Returns:
        dict: A properly formatted Slack Block Kit element dictionary.

    Raises:
        ValueError: If block_type is not supported or if parameters are invalid.

    Examples:
        # Create a button
        button = create_blockkit(
            'button',
            text={"type": "plain_text", "text": "Submit"},
            action_id="submit_btn",
            style="primary"
        )

        # Create a select menu
        select = create_blockkit(
            'static_select',
            action_id="choice_menu",
            placeholder={"type": "plain_text", "text": "Choose an option"},
            options=[
                {"text": {"type": "plain_text", "text": "Option 1"}, "value": "1"},
                {"text": {"type": "plain_text", "text": "Option 2"}, "value": "2"}
            ]
        )

        # Create a text input
        input_field = create_blockkit(
            'plain_text_input',
            action_id="user_input",
            placeholder={"type": "plain_text", "text": "Enter your message"},
            multiline=True
        )

        # Create an image
        image = create_blockkit(
            'image',
            alt_text="A beautiful sunset",
            image_url="https://example.com/sunset.jpg"
        )

        # Create a date picker
        date_picker = create_blockkit(
            'datepicker',
            action_id="meeting_date",
            placeholder={"type": "plain_text", "text": "Select a date"},
            initial_date="2024-01-15"
        )
    """
    block_type = block_type.lower()
    
    # Validate block type
    valid_types = {
        'button', 'static_select', 'plain_text_input', 'datepicker', 'image',
        'option', 'confirm', 'option_group', 'text', 'file'
    }
    
    if block_type not in valid_types:
        raise ValueError(
            f"Unsupported block_type '{block_type}'. "
            f"Valid types are: {', '.join(sorted(valid_types))}"
        )
    
    # Create the base block structure
    block = {"type": block_type}
    
    # Add all provided parameters
    block.update(kwargs)
    
    # Basic validation based on block type
    self._validate_blockkit_structure(block, block_type)
    
    return block`

I'm slightly worried that the docstring is too complicated for the agent to consistently produce blockkits, there are quite a lot of validation checks that we need to worry about in terms of the lengths and options of the different blockkit text fields. They're quite complicated so I assume that you'ld want them completely handled in a helper function. My worry is that without including the validation rules in the docstring the LLM might write incorrect text fields pretty often, but since the format rules are so complicated I don't know whether it's feasible to include them in the docstring.

waleedalzarooni avatar Aug 24 '25 11:08 waleedalzarooni

Hey @fengju0213, I had a think about some approaches to unify the methods, let me know what you think about these two options!

    def create_a_blockkit(
    self,
    block_type: str,
    **kwargs
) -> dict:
    r"""Creates various Slack Block Kit elements based on the specified type.

    This unified method creates different Block Kit elements by specifying the
    block_type and providing the appropriate parameters for each type.

    Args:
        block_type (str): The type of Block Kit element to create. Valid types:
            - 'button': Creates a button element
            - 'select_menu': Creates a static select menu
            - 'plain_text_input': Creates a plain text input field
            - 'date_picker': Creates a date picker
            - 'image': Creates an image block
            - 'option': Creates an option for select menus
            - 'confirm_object': Creates a confirmation dialog object
            - 'option_group': Creates an option group for select menus
            - 'text_object': Creates a text object (plain_text or mrkdwn)
            - 'slack_file_object': Creates a Slack file object
        **kwargs: Parameters specific to the block_type. See individual method
            documentation for required and optional parameters.

    Returns:
        dict: A dictionary representing the specified Slack Block Kit element.

    Raises:
        ValueError: If block_type is not supported or if required parameters
            are missing/invalid for the specified type.

    Examples:
        # Create a button
        button = create_a_blockkit(
            'button',
            text={"type": "plain_text", "text": "Click me"},
            action_id="button_1"
        )

        # Create a select menu
        select_menu = create_a_blockkit(
            'select_menu',
            placeholder={"type": "plain_text", "text": "Choose an option"},
            options=[
                {"text": {"type": "plain_text", "text": "Option 1"}, "value": "1"},
                {"text": {"type": "plain_text", "text": "Option 2"}, "value": "2"}
            ]
        )

        # Create a text input
        text_input = create_a_blockkit(
            'plain_text_input',
            action_id="input_1",
            placeholder={"type": "plain_text", "text": "Enter text here"}
        )
    """
    block_type = block_type.lower()
    
    # Map block types to their creation methods
    block_creators = {
        'button': self._create_button,
        'select_menu': self._create_select_menu,
        'plain_text_input': self._create_plain_text_input,
        'date_picker': self._create_date_picker,
        'image': self._create_image,
        'option': self._create_option,
        'confirm_object': self._create_confirm_object,
        'option_group': self._create_option_group,
        'text_object': self._create_text_object,
        'slack_file_object': self._create_slack_file_object,
    }
    
    if block_type not in block_creators:
        valid_types = ', '.join(block_creators.keys())
        raise ValueError(
            f"Unsupported block_type '{block_type}'. "
            f"Valid types are: {valid_types}"
        )
    
    # Call the appropriate internal method
    return block_creators[block_type](**kwargs)`

This approach still utilises the internal methods that I wrote so maybe not the agent led approach you prefer, alternatively this second approach will provide much more responsibility to the agent and may provide the docstring instructions strategy you're referring to

   `def create_blockkit(
    self,
    block_type: str,
    **kwargs
) -> dict:
    r"""Creates Slack Block Kit elements by providing the block type and parameters.

    This method allows you to create any Slack Block Kit element by specifying the
    block_type and providing the appropriate parameters. The method validates the
    structure and returns a properly formatted Block Kit dictionary.

    SUPPORTED BLOCK TYPES AND THEIR PARAMETERS:

    1. BUTTON ('button'):
       Required: text (dict with 'type' and 'text' keys)
       Optional: action_id, value, style ('primary' or 'danger'), url, confirm, accessibility_label
       Example: {"type": "button", "text": {"type": "plain_text", "text": "Click me"}, "action_id": "btn1"}

    2. SELECT MENU ('static_select'):
       Required: placeholder (dict with 'type' and 'text' keys)
       Optional: action_id, options (list), option_groups (list), initial_option, confirm, focus_on_load
       Example: {"type": "static_select", "placeholder": {"type": "plain_text", "text": "Choose..."}, "options": [...]}

    3. PLAIN TEXT INPUT ('plain_text_input'):
       Optional: action_id, multiline, placeholder, initial_value, min_length, max_length, dispatch_action_config, focus_on_load
       Example: {"type": "plain_text_input", "action_id": "input1", "multiline": true}

    4. DATE PICKER ('datepicker'):
       Optional: action_id, placeholder, initial_date (YYYY-MM-DD), confirm, focus_on_load
       Example: {"type": "datepicker", "action_id": "date1", "initial_date": "2024-01-15"}

    5. IMAGE ('image'):
       Required: alt_text (string)
       Optional: image_url, slack_file (dict with 'id' or 'url')
       Example: {"type": "image", "alt_text": "Description", "image_url": "https://..."}

    6. OPTION ('option'):
       Required: text (dict), value (string)
       Optional: description, url
       Example: {"text": {"type": "plain_text", "text": "Option 1"}, "value": "1"}

    7. CONFIRM OBJECT ('confirm'):
       Required: title, text, confirm, deny (all dict objects)
       Optional: style ('primary' or 'danger')
       Example: {"title": {"type": "plain_text", "text": "Title"}, "text": {...}, "confirm": {...}, "deny": {...}}

    8. OPTION GROUP ('option_group'):
       Required: label (dict), options (list of option objects)
       Example: {"label": {"type": "plain_text", "text": "Group 1"}, "options": [...]}

    9. TEXT OBJECT ('text'):
       Required: type ('plain_text' or 'mrkdwn'), text (string)
       Optional: emoji (for plain_text), verbatim (for mrkdwn)
       Example: {"type": "plain_text", "text": "Hello world", "emoji": true}

    10. SLACK FILE OBJECT ('file'):
        Required: Either 'id' or 'url' (string)
        Example: {"id": "F1234567890"} or {"url": "https://..."}

    TEXT OBJECT FORMAT:
    All text objects should be dictionaries with this structure:
    - For plain_text: {"type": "plain_text", "text": "your text here"}
    - For mrkdwn: {"type": "mrkdwn", "text": "your *markdown* text here"}

    VALIDATION RULES:
    - Text objects must have 'type' and 'text' keys
    - Character limits: button text (75), placeholder (150), action_id (255), etc.
    - Style values must be 'primary' or 'danger'
    - Date format must be YYYY-MM-DD
    - URLs must be valid strings
    - Lists (options, option_groups) have maximum limits

    Args:
        block_type (str): The type of Block Kit element to create. Must be one of:
            'button', 'static_select', 'plain_text_input', 'datepicker', 'image',
            'option', 'confirm', 'option_group', 'text', 'file'
        **kwargs: Parameters for the block as key-value pairs.

    Returns:
        dict: A properly formatted Slack Block Kit element dictionary.

    Raises:
        ValueError: If block_type is not supported or if parameters are invalid.

    Examples:
        # Create a button
        button = create_blockkit(
            'button',
            text={"type": "plain_text", "text": "Submit"},
            action_id="submit_btn",
            style="primary"
        )

        # Create a select menu
        select = create_blockkit(
            'static_select',
            action_id="choice_menu",
            placeholder={"type": "plain_text", "text": "Choose an option"},
            options=[
                {"text": {"type": "plain_text", "text": "Option 1"}, "value": "1"},
                {"text": {"type": "plain_text", "text": "Option 2"}, "value": "2"}
            ]
        )

        # Create a text input
        input_field = create_blockkit(
            'plain_text_input',
            action_id="user_input",
            placeholder={"type": "plain_text", "text": "Enter your message"},
            multiline=True
        )

        # Create an image
        image = create_blockkit(
            'image',
            alt_text="A beautiful sunset",
            image_url="https://example.com/sunset.jpg"
        )

        # Create a date picker
        date_picker = create_blockkit(
            'datepicker',
            action_id="meeting_date",
            placeholder={"type": "plain_text", "text": "Select a date"},
            initial_date="2024-01-15"
        )
    """
    block_type = block_type.lower()
    
    # Validate block type
    valid_types = {
        'button', 'static_select', 'plain_text_input', 'datepicker', 'image',
        'option', 'confirm', 'option_group', 'text', 'file'
    }
    
    if block_type not in valid_types:
        raise ValueError(
            f"Unsupported block_type '{block_type}'. "
            f"Valid types are: {', '.join(sorted(valid_types))}"
        )
    
    # Create the base block structure
    block = {"type": block_type}
    
    # Add all provided parameters
    block.update(kwargs)
    
    # Basic validation based on block type
    self._validate_blockkit_structure(block, block_type)
    
    return block`

I'm slightly worried that the docstring is too complicated for the agent to consistently produce blockkits, there are quite a lot of validation checks that we need to worry about in terms of the lengths and options of the different blockkit text fields. They're quite complicated so I assume that you'ld want them completely handled in a helper function. My worry is that without including the validation rules in the docstring the LLM might write incorrect text fields pretty often, but since the format rules are so complicated I don't know whether it's feasible to include them in the docstring.

@waleedalzarooni thank you very much for your preliminary research on these two approaches. I think your concern is completely reasonable, and I also lean towards the first approach you mentioned, as it not only provides a unified method but also ensures considerable robustness.

fengju0213 avatar Aug 26 '25 13:08 fengju0213

thanks @waleedalzarooni and @fengju0213 !

I would like to propose three principles to ensure the toolkit is robust and user-friendly for agents:

  1. prefer Explicit over Implicit Arguments: To ensure reliable use by an agent (LLM), function signatures should be fully explicit. We should avoid **kwargs in favor of clearly defined, named arguments. This minimizes ambiguity and potential errors when the agent passes parameters
  2. utilize Precise and Standardized Type Hints: We should consistently use the typing library to provide specific and informative type hints. For collection types like dictionaries, it's crucial to define their internal structure (e.g., use Dict[str, str] instead of the generic dict).
  3. for the added function, test it with ChatAgent to ensure agent could correctly understand and execute the function

could @waleedalzarooni adjust the design based on these principles and also add these content to our contribution guideline? Thanks in advance!

Wendong-Fan avatar Aug 27 '25 23:08 Wendong-Fan

@Wendong-Fan

Sounds good, will work on implementing the new approach ASAP.

waleedalzarooni avatar Aug 28 '25 10:08 waleedalzarooni

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

Hi @Wendong-Fan @fengju0213

I have spent some time experimenting with different approaches and found that the agent is quite capable of forming correct blockkit solely through an example based system message, This new commit highlights this new approach, let me know what you think. I've also included a testing script which should illustrate the reliability of the new approach.

It's only 80% implemented if you like this style I can finish adding additional validation checks and refactoring!

My apologies, first commit only included test file, second includes changes to slack_toolkit

Also I over wrote the previous commits since during my first commit there was a rebasing error which generated 150 extra commits on the branch, needed to cover up the noob error!

waleedalzarooni avatar Aug 31 '25 15:08 waleedalzarooni

@Wendong-Fan @fengju0213

requested amendments completed, sorry about the changes of ChatCompletionMessageFunctionToolCall I made the change to allow for the test function to work but I changed it back locally, not sure why the commit didn't log the change back

waleedalzarooni avatar Sep 01 '25 10:09 waleedalzarooni

@waleedalzarooni It is basically a complete implementation. i submitted a PR to enhance it. Because the method we added is an advanced method, we can let users choose whether to use it.

fengju0213 avatar Sep 05 '25 10:09 fengju0213