mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-16 17:36:07 +00:00
feat: add supports for dify hitl (#2226)
* feat: Implement workflow form handling for paused workflows - Added module-level storage for pending forms to manage state across sessions. - Introduced functions to set, get, and clear pending forms with expiration handling. - Enhanced DifyServiceAPIRunner to support resuming paused workflows via form actions. - Implemented logic to yield human input requests and display appropriate messages. - Updated workflow submission methods to handle paused states and resume actions. - Ensured proper merging of pending form actions with user inputs for seamless interaction. * feat: Add '_routed_by_rule' variable to form action in Lark and Telegram adapters * feat: Enhance Lark and Telegram adapters with new form handling for paused workflows * feat: Enhance TelegramAdapter to handle form action buttons and message threading * feat: Improve TelegramAdapter message handling with enhanced error management and draft message support * feat: Add the function for formatting human input text to support adapters without rich UI. * feat(dingtalk): implement human input card support and card action handling - Add a new module `card_callback.py` to handle card action button clicks from DingTalk. - Introduce `DingTalkCardActionHandler` to process card action callbacks and extract parameters. - Update `DingTalkAdapter` to manage card state and handle form input through a single card template. - Add configuration for `human_input_card_template_id` in `dingtalk.yaml` to specify the template for human input. - Create a new card template `dingtalk_human_input_card.json` for rendering human input prompts and buttons. * feat(dingtalk): enhance human input card functionality with streaming support and active turn management - Updated the DingTalk card template to enable streaming mode and multi-update configuration. - Removed the obsolete delete_card method from DingTalkClient to streamline card management. - Enhanced DingTalkAdapter to manage active turn cards and accumulated streaming text, ensuring a seamless user experience during human input prompts. - Modified the create_message_card method to utilize existing active cards for resumed workflows, preventing duplication. - Improved the _paint_form_on_card method to update existing cards with human input prompts and buttons dynamically. - Updated the dingtalk_human_input_card.json template to reflect the new streaming capabilities and configuration options. * feat(wecom): implement Dify human input pause handling with button interaction support * feat(qqofficial): implement Dify human input button interaction handling and markdown keyboard support * feat(qqofficial): implement one-click QR binding and enhance localization support * feat(discord): implement Discord form view with button interactions for Dify actions * fix(telegram): correct group chat type check and handle oversized callback data for Telegram actions fix(difysvapi): ensure safe access to remove-think configuration in pipeline settings * feat(dify): add support for chatflow app type and enhance human input handling * feat(telegram): add action title feedback for user selections in Telegram messages * feat(lark): enhance LarkAdapter to store form content for resume notices * feat(dingtalk): update display formatting for card content with HTML line breaks * feat(dingtalk): add feedback functionality to cards with 👍/👎 buttons - Implemented feedback state management for cards, allowing users to provide feedback via thumbs up/down buttons. - Enhanced card rendering to include feedback buttons when appropriate. - Registered feedback listeners to handle feedback events and update card states accordingly. - Updated the card template to support dynamic button rendering for feedback actions. - Improved error handling and logging for feedback actions and card updates. * fix: add Avatar component to dingtalk_human_input_card.json for enhanced user interaction * feat(wecom): add optional source block to interactive template cards for enhanced branding * feat(wecom): add functions for template card action extraction and update, enhance button interaction handling * feat(qqofficial): synchronize passive-reply counter with inbound message sequence * feat(qqofficial): add method to identify invisible form placeholder chunks in messages * feat(dingtalk): add download link for human input card template and enhance dynamic form configuration * feat(telegram): enhance message handling with group stream deletion and form placeholder detection * Add unit tests for DingTalk, Lark, WeComBot, and Dify service API runners - Implement tests for DingTalk adapter helper functions including form content cleaning, input extraction, and completed input lines. - Create unit tests for Lark adapter helper functions focusing on input extraction and completed input lines. - Add tests for WeComBot template card functionalities, including event extraction and payload building for human input. - Enhance Dify service API runner tests to cover human input forms, including input collection, action handling, and form snapshot extraction. * feat: Enhance Telegram and QQ Official adapters with select field handling and form action processing - Added support for select fields in Telegram adapter, including option extraction and callback handling. - Implemented form action processing for Telegram callbacks, improving user interaction feedback. - Introduced new helper functions for building keyboards and resolving select button actions in QQ Official adapter. - Enhanced DifyServiceAPIRunner to handle cumulative streaming responses and improve error handling during workflow resumes. - Added unit tests for new functionalities in Telegram and QQ Official adapters, ensuring robust behavior for select fields and form actions. * feat(lark): add functions for current input definitions and visible form content handling feat(qqofficial): update fallback text handling for non-streaming scenarios feat(difysvapi): enhance form content processing for interactive fields and actions test: add unit tests for Lark and QQ Official adapter functionalities * Add tests for DingTalk adapter content processing and markdown formatting - Updated the assertion in `test_dingtalk_completed_input_lines_include_text_and_select_values` to remove unnecessary markdown formatting. - Added new tests to verify that `_dingtalk_clean_form_content` maintains the order of prompts and completed values in various scenarios. - Introduced `test_dingtalk_card_markdown_preserves_internal_line_breaks` to ensure internal line breaks are correctly converted to HTML line breaks. * feat: Refactor input handling and feedback messages across multiple adapters * feat: Update the human-computer interaction template cards, and optimize the prompt information and content display. * feat: Refactor pending form handling to isolate by bot and pipeline * feat: Enhance error handling and caching for Dify and WeCom interactions * feat: Enhance select input handling and validation in Dify API runner and Telegram adapter * feat: Add missing completed input lines handling in DingTalk adapter * feat: Add pipeline_uuid handling across multiple adapters and update related tests
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
"""Tests for Lark adapter helper behavior."""
|
||||
|
||||
from langbot.pkg.platform.sources.lark import (
|
||||
LarkAdapter,
|
||||
_lark_clean_form_content,
|
||||
_lark_completed_input_lines,
|
||||
_lark_current_input_defs,
|
||||
_lark_extract_action_form_inputs,
|
||||
_lark_should_update_stream_element,
|
||||
_lark_visible_form_content,
|
||||
)
|
||||
|
||||
|
||||
def test_lark_current_input_defs_only_returns_active_stage():
|
||||
input_defs = [
|
||||
{'output_variable_name': 'us_input', 'type': 'paragraph'},
|
||||
{'output_variable_name': 'xiala', 'type': 'select'},
|
||||
]
|
||||
|
||||
assert _lark_current_input_defs(
|
||||
{
|
||||
'_current_input_field': 'xiala',
|
||||
'input_defs': input_defs,
|
||||
}
|
||||
) == [input_defs[1]]
|
||||
assert (
|
||||
_lark_current_input_defs(
|
||||
{
|
||||
'_action_select_only': True,
|
||||
'input_defs': input_defs,
|
||||
}
|
||||
)
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_lark_form_field_elements_only_render_active_stage():
|
||||
adapter = LarkAdapter.model_construct()
|
||||
form_data = {
|
||||
'_current_input_field': 'xiala',
|
||||
'input_defs': [
|
||||
{'output_variable_name': 'us_input', 'type': 'paragraph'},
|
||||
{
|
||||
'output_variable_name': 'xiala',
|
||||
'type': 'select',
|
||||
'option_source': {'type': 'constant', 'value': ['1', '2']},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
elements, input_name_map, file_help_lines = adapter._build_lark_form_field_elements(form_data)
|
||||
|
||||
assert len(elements) == 1
|
||||
assert elements[0]['tag'] == 'select_static'
|
||||
assert elements[0]['label']['content'] == 'xiala'
|
||||
assert list(input_name_map.values()) == ['xiala']
|
||||
assert file_help_lines == []
|
||||
|
||||
|
||||
def test_lark_form_stage_skips_closed_streaming_element_update():
|
||||
assert not _lark_should_update_stream_element(
|
||||
resume_from=False,
|
||||
form_data={'_current_input_field': 'xiala'},
|
||||
msg_seq=1,
|
||||
is_final=True,
|
||||
)
|
||||
assert _lark_should_update_stream_element(
|
||||
resume_from=False,
|
||||
form_data=None,
|
||||
msg_seq=1,
|
||||
is_final=True,
|
||||
)
|
||||
|
||||
|
||||
def test_lark_final_action_stage_interleaves_prompts_and_completed_values():
|
||||
form_content = _lark_visible_form_content(
|
||||
{
|
||||
'_action_select_only': True,
|
||||
'raw_form_content': ('11\nQuestion\n{{#$output.us_input#}}\nChoose an answer\n{{#$output.xiala#}}\n'),
|
||||
'all_input_defs': [
|
||||
{'output_variable_name': 'us_input', 'type': 'paragraph'},
|
||||
{'output_variable_name': 'xiala', 'type': 'select'},
|
||||
],
|
||||
'inputs': {'us_input': 'hello', 'xiala': '2'},
|
||||
}
|
||||
)
|
||||
|
||||
assert '{{#$output.' not in form_content
|
||||
assert form_content.startswith('11\nQuestion')
|
||||
assert form_content.index('Question') < form_content.index('us_input')
|
||||
assert form_content.index('us_input') < form_content.index('Choose an answer')
|
||||
assert form_content.index('Choose an answer') < form_content.index('xiala')
|
||||
|
||||
|
||||
def test_lark_completed_input_lines_include_text_select_and_files():
|
||||
lines = _lark_completed_input_lines(
|
||||
{
|
||||
'all_input_defs': [
|
||||
{'output_variable_name': 'us_input', 'type': 'paragraph'},
|
||||
{'output_variable_name': 'xiala', 'type': 'select'},
|
||||
{'output_variable_name': 'files', 'type': 'file-list'},
|
||||
],
|
||||
'inputs': {
|
||||
'us_input': '你好',
|
||||
'xiala': 'or',
|
||||
'files': [{'upload_file_id': 'file-1'}, {'upload_file_id': 'file-2'}],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert lines == [
|
||||
'✅ us_input:你好',
|
||||
'✅ xiala:or',
|
||||
'✅ files:2 file(s)',
|
||||
]
|
||||
|
||||
|
||||
def test_lark_clean_form_content_removes_all_input_placeholders():
|
||||
content = _lark_clean_form_content(
|
||||
'人工介入\n\n{{#$output.us_input#}}\n\n{{#$output.xiala#}}\n',
|
||||
[
|
||||
{'output_variable_name': 'us_input', 'type': 'paragraph'},
|
||||
{'output_variable_name': 'xiala', 'type': 'select'},
|
||||
],
|
||||
)
|
||||
|
||||
assert content == '人工介入'
|
||||
|
||||
|
||||
def test_lark_extract_action_form_inputs_from_json_form_value():
|
||||
class Action:
|
||||
form_value = '{"Input_1_us_input_abcd12": "hello", "Select_2_xiala_abcd12": "B"}'
|
||||
input_value = None
|
||||
option = None
|
||||
name = None
|
||||
|
||||
inputs = _lark_extract_action_form_inputs(
|
||||
Action(),
|
||||
{
|
||||
'input_name_map': {
|
||||
'Input_1_us_input_abcd12': 'us_input',
|
||||
'Select_2_xiala_abcd12': 'xiala',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert inputs == {'us_input': 'hello', 'xiala': 'B'}
|
||||
|
||||
|
||||
def test_lark_extract_action_form_inputs_from_webhook_dict_action():
|
||||
inputs = _lark_extract_action_form_inputs(
|
||||
{
|
||||
'form_value': {
|
||||
'Input_1_us_input_abcd12': 'hello',
|
||||
'Select_2_xiala_abcd12': {'value': 'B', 'text': {'content': 'Option B'}},
|
||||
}
|
||||
},
|
||||
{
|
||||
'input_name_map': {
|
||||
'Input_1_us_input_abcd12': 'us_input',
|
||||
'Select_2_xiala_abcd12': 'xiala',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert inputs == {'us_input': 'hello', 'xiala': {'value': 'B', 'text': {'content': 'Option B'}}}
|
||||
|
||||
|
||||
def test_lark_extract_action_form_inputs_maps_dotted_component_names():
|
||||
inputs = _lark_extract_action_form_inputs(
|
||||
{
|
||||
'form_value': {
|
||||
'Form_1_token_abcd12.Input_1_us_input_abcd12': 'hello',
|
||||
}
|
||||
},
|
||||
{
|
||||
'input_name_map': {
|
||||
'Input_1_us_input_abcd12': 'us_input',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert inputs == {'us_input': 'hello'}
|
||||
|
||||
|
||||
def test_lark_completed_input_lines_display_select_value_from_object():
|
||||
lines = _lark_completed_input_lines(
|
||||
{
|
||||
'all_input_defs': [
|
||||
{'output_variable_name': 'xiala', 'type': 'select'},
|
||||
],
|
||||
'inputs': {'xiala': {'value': 'B', 'text': {'content': 'Option B'}}},
|
||||
}
|
||||
)
|
||||
|
||||
assert lines == ['✅ xiala:B']
|
||||
Reference in New Issue
Block a user