feat: blockkit integration into send_slack_message method for feature request #2839
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-numberin the PR description (required) - [X ] I have checked if any dependencies need to be added or updated in
pyproject.tomlanduv 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!
[!IMPORTANT]
Review skipped
Auto reviews are disabled on this repository.
Please check the settings in the CodeRabbit UI or the
.coderabbit.yamlfile in this repository. To trigger a single review, invoke the@coderabbitai reviewcommand.You can disable this status message by setting the
reviews.review_statustofalsein 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.
🪧 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
@coderabbitaiin a new review comment at the desired location with your query. - PR comments: Tag
@coderabbitaiin 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 ignoreor@coderabbit ignoreanywhere in the PR description to prevent this PR from being reviewed. - Add
@coderabbitai summaryto generate the high-level summary at a specific location in the PR description. - Add
@coderabbitaianywhere in the PR title to generate the title automatically.
CodeRabbit Configuration File (.coderabbit.yaml)
- You can programmatically configure CodeRabbit by adding a
.coderabbit.yamlfile 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.
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
@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!
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.
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.
thanks @waleedalzarooni and @fengju0213 !
I would like to propose three principles to ensure the toolkit is robust and user-friendly for agents:
- prefer Explicit over Implicit Arguments: To ensure reliable use by an agent (LLM), function signatures should be fully explicit. We should avoid
**kwargsin favor of clearly defined, named arguments. This minimizes ambiguity and potential errors when the agent passes parameters - 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 genericdict). - 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
Sounds good, will work on implementing the new approach ASAP.
Check out this pull request on ![]()
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!
@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 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.