diff --git a/integrations/extensions/README.md b/integrations/extensions/README.md index c54e66b2..ba9b0de9 100644 --- a/integrations/extensions/README.md +++ b/integrations/extensions/README.md @@ -40,6 +40,7 @@ Then we have our **additional starter kits**. These focus on showcasing the brea - [LLM use case: Summarization](./starter-kits/language-model-summarization/) - [Magnolia](./starter-kits/magnolia/) - [Mailchimp Campaign Management](./starter-kits/mailchimp/) + - [MS Teams File Upload](./starter-kits/ms-teams-file-upload/) - [Service Now](./starter-kits/servicenow/) - [Spotify](./starter-kits/spotify/) - [Test It ALL](./starter-kits/testitall/) diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/action-skill.json b/integrations/extensions/starter-kits/ms-teams-file-upload/action-skill.json new file mode 100644 index 00000000..358f7cc0 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/action-skill.json @@ -0,0 +1,2388 @@ +{ + "name": "1204 Demo", + "type": "action", + "counts": { + "actions": 9, + "intents": 6, + "entities": 3, + "data_types": 0, + "collections": 0, + "global_variables": 3 + }, + "status": "Available", + "language": "en", + "skill_id": "74e13869-e22f-4132-af6a-6a9fafddd5e6", + "workspace": { + "actions": [ + { + "steps": [ + { + "step": "digression_failure", + "output": { + "generic": [ + { + "values": [ + { + "text": "Sorry I couldn't confirm if you wanted to return to previous topic, let me connect to an agent." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "connect_to_agent", + "response": { + "transfer_info": { + "target": {} + }, + "agent_available": { + "message": "Let's send you to an available agent." + }, + "agent_unavailable": { + "message": "There are no agents available at this time. When one becomes available, we'll connect you." + }, + "message_to_human_agent": "" + } + }, + "variable": "digression_failure", + "condition": { + "eq": [ + { + "system_variable": "fallback_reason" + }, + { + "scalar": "Failed to confirm topic return" + } + ] + }, + "next_step": "step_001" + }, + { + "step": "step_001", + "output": { + "generic": [ + { + "values": [ + { + "text": "I'm afraid I don't understand. I can connect you to an agent." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "connect_to_agent", + "response": { + "transfer_info": { + "target": {} + }, + "agent_available": { + "message": "Let's send you to an available agent." + }, + "agent_unavailable": { + "message": "There are no agents available at this time. When one becomes available, we'll connect you." + }, + "message_to_human_agent": "" + } + }, + "variable": "step_001", + "condition": { + "eq": [ + { + "system_variable": "fallback_reason" + }, + { + "scalar": "Step validation failed" + } + ] + }, + "next_step": "step_002" + }, + { + "step": "step_002", + "output": { + "generic": [ + { + "values": [ + { + "text": "Sorry I couldn't assist you. I will connect you to an agent right away." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "connect_to_agent", + "response": { + "transfer_info": { + "target": {} + }, + "agent_available": { + "message": "Let's send you to an available agent." + }, + "agent_unavailable": { + "message": "There are no agents available at this time. When one becomes available, we'll connect you." + }, + "message_to_human_agent": "" + } + }, + "variable": "step_002", + "condition": { + "eq": [ + { + "system_variable": "fallback_reason" + }, + { + "scalar": "Agent requested" + } + ] + }, + "next_step": "step_003" + }, + { + "step": "step_003", + "output": { + "generic": [ + { + "values": [ + { + "text": "I am afraid I do not understand what you are asking, let me connect you to an agent." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "connect_to_agent", + "response": { + "transfer_info": { + "target": {} + }, + "agent_available": { + "message": "Let's send you to an available agent." + }, + "agent_unavailable": { + "message": "There are no agents available at this time. When one becomes available, we'll connect you." + }, + "message_to_human_agent": "" + } + }, + "variable": "step_003", + "condition": { + "eq": [ + { + "system_variable": "fallback_reason" + }, + { + "scalar": "No action matches" + } + ] + }, + "next_step": "step_004" + }, + { + "step": "step_004", + "output": { + "generic": [ + { + "values": [ + { + "text": "It seems this conversation would be best managed by a human agent. Let me connect you to one of our agents." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "connect_to_agent", + "response": { + "transfer_info": { + "target": {} + }, + "agent_available": { + "message": "Let's send you to an available agent." + }, + "agent_unavailable": { + "message": "There are no agents available at this time. When one becomes available, we'll connect you." + }, + "message_to_human_agent": "" + } + }, + "variable": "step_004", + "condition": { + "eq": [ + { + "system_variable": "fallback_reason" + }, + { + "scalar": "Danger word detected" + } + ] + }, + "next_step": "step_005" + }, + { + "step": "step_005", + "output": { + "generic": [ + { + "values": [ + { + "text": "It seems this conversation would be best managed by a human agent. Let me connect you to one of our agents." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "connect_to_agent", + "response": { + "transfer_info": { + "target": {} + }, + "agent_available": { + "message": "Let's send you to an available agent." + }, + "agent_unavailable": { + "message": "There are no agents available at this time. When one becomes available, we'll connect you." + }, + "message_to_human_agent": "" + } + }, + "variable": "step_005", + "condition": { + "eq": [ + { + "system_variable": "fallback_reason" + }, + { + "scalar": "Profanity detected" + } + ] + } + } + ], + "title": "Fallback", + "action": "fallback", + "boosts": [], + "handlers": [], + "condition": { + "intent": "fallback_connect_to_agent" + }, + "variables": [ + { + "title": "Topic return failed", + "variable": "digression_failure", + "data_type": "any" + }, + { + "title": "I'm afraid I don't understand. I can connect you to an agent.", + "variable": "step_001", + "data_type": "any" + }, + { + "title": "Sorry I couldn't assist you. I will connect you to an agent righ", + "variable": "step_002", + "data_type": "any" + }, + { + "title": "I am afraid I do not understand what you are asking, let me conn", + "variable": "step_003", + "data_type": "any" + }, + { + "title": "It seems this conversation would be best managed", + "variable": "step_004", + "data_type": "any" + }, + { + "title": "Profanity - It seems this conversation", + "variable": "step_005", + "data_type": "any" + } + ], + "next_action": "run_always", + "disambiguation_opt_out": true + }, + { + "steps": [ + { + "step": "danger_word_detected", + "title": "Connect to agent", + "handlers": [], + "resolver": { + "type": "fallback" + }, + "variable": "danger_word_detected_variable", + "condition": { + "entity": "danger_words" + }, + "next_step": "profanity_detected" + }, + { + "step": "profanity_detected", + "title": "Show warning", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Please use appropriate language when interacting with the assistant." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [ + { + "type": "max_hits", + "handler": "max_hits_handler", + "resolver": { + "type": "fallback" + } + } + ], + "max_hits": 2, + "resolver": { + "type": "end_action" + }, + "variable": "profanity_detected_variable", + "condition": { + "entity": "profane_words" + } + } + ], + "title": "Trigger word detected", + "action": "run_always", + "boosts": [], + "handlers": [], + "variables": [ + { + "title": "Profanity detected", + "variable": "danger_word_detected_variable", + "data_type": "any" + }, + { + "title": "Profane word detected", + "variable": "profanity_detected_variable", + "data_type": "any" + } + ], + "next_action": "anything_else" + }, + { + "steps": [ + { + "step": "step_001", + "output": { + "generic": [ + { + "values": [ + { + "text": "Welcome, how can I assist you?" + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_001" + } + ], + "title": "Greet customer", + "action": "welcome", + "boosts": [], + "handlers": [], + "condition": { + "expression": "welcome" + }, + "variables": [ + { + "variable": "step_001", + "data_type": "any" + } + ], + "next_action": "action_9343", + "disambiguation_opt_out": true + }, + { + "steps": [ + { + "step": "step_144", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Please upload document(s) that you want me to analyze:" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "context": { + "variables": [] + }, + "handlers": [], + "question": { + "free_text": true + }, + "resolver": { + "type": "continue" + }, + "variable": "step_144", + "next_step": "step_316" + }, + { + "step": "step_316", + "context": { + "variables": [ + { + "value": { + "expression": "input.attachments" + }, + "skill_variable": "attachments" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_316", + "next_step": "step_652" + }, + { + "step": "step_652", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Reading uploaded files..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "context": { + "variables": [] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/upload", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "skill_variable": "attachments" + }, + "parameter": "attachments" + }, + { + "value": { + "skill_variable": "session_id" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_652_result_1" + } + }, + "variable": "step_652", + "next_step": "step_218" + }, + { + "step": "step_218", + "context": { + "variables": [ + { + "value": { + "expression": "[]" + }, + "skill_variable": "attachments" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_218", + "next_step": "step_994" + }, + { + "step": "step_994", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Uploading failed.\n\n\n\n" + }, + { + "variable": "step_652_result_1", + "variable_path": "body.message" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_994", + "condition": { + "eq": [ + { + "variable": "step_652_result_1", + "variable_path": "body.success" + }, + { + "scalar": false + } + ] + }, + "next_step": "step_303" + }, + { + "step": "step_303", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "" + }, + { + "variable": "step_652_result_1", + "variable_path": "body.message" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "context": { + "variables": [ + { + "value": { + "variable": "step_652_result_1", + "variable_path": "body.session_id" + }, + "skill_variable": "session_id" + } + ] + }, + "handlers": [], + "resolver": { + "type": "invoke_another_action_and_end", + "invoke_action": { + "action": "action_8715", + "policy": "default", + "parameters": null, + "result_variable": "step_303_result_1" + } + }, + "variable": "step_303", + "condition": { + "eq": [ + { + "variable": "step_652_result_1", + "variable_path": "body.success" + }, + { + "scalar": true + } + ] + } + } + ], + "title": "Upload", + "action": "action_12706", + "boosts": [], + "handlers": [], + "condition": { + "intent": "action_12706_intent_17003" + }, + "variables": [ + { + "title": "Please upload document(s) that you want me to analyze:", + "privacy": { + "enabled": false + }, + "variable": "step_144", + "data_type": "any" + }, + { + "title": "No response", + "privacy": { + "enabled": false + }, + "variable": "step_218", + "data_type": "any" + }, + { + "title": "{variable}", + "privacy": { + "enabled": false + }, + "variable": "step_303", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_303_result_1", + "data_type": "any" + }, + { + "title": "No response", + "privacy": { + "enabled": false + }, + "variable": "step_316", + "data_type": "any" + }, + { + "title": "Reading uploaded files...", + "privacy": { + "enabled": false + }, + "variable": "step_652", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_652_result_1", + "data_type": "any" + }, + { + "title": "Uploading failed. {variable}", + "privacy": { + "enabled": false + }, + "variable": "step_994", + "data_type": "any" + } + ], + "launch_mode": "learning", + "next_action": "action_34313-2", + "topic_switch": { + "allowed_from": true, + "allowed_into": true, + "never_return": false + }, + "disambiguation_opt_out": false + }, + { + "steps": [ + { + "step": "step_485", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Checking the Watson Discovery processing status of uploaded files..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/docs_status", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "skill_variable": "session_id" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_485_result_1" + } + }, + "variable": "step_485", + "next_step": "step_782" + }, + { + "step": "step_782", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Please upload your docs before proceed." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_782", + "condition": { + "expression": "${step_485_result_1.body.options}.length == 0" + }, + "next_step": "step_555" + }, + { + "step": "step_555", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Which file would you like me to summarize? Please select one of the following files:" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + }, + { + "response_type": "response_from_variable", + "repeat_on_reprompt": true + } + ] + }, + "context": { + "variables": [ + { + "value": { + "variable": "step_485_result_1", + "variable_path": "body.session_id" + }, + "skill_variable": "session_id" + } + ] + }, + "handlers": [ + { + "type": "not_found", + "title": "validation_not_found_handler", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "I didn't catch that. Select a valid option:" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "incremental" + } + ] + }, + "handler": "validation_not_found_handler", + "resolver": { + "type": "prompt_again" + }, + "next_handler": "validation_not_found_max_tries_handler" + }, + { + "type": "not_found_max_tries", + "title": "validation_not_found_max_tries_handler", + "handler": "validation_not_found_max_tries_handler", + "resolver": { + "type": "fallback" + } + } + ], + "question": { + "max_tries": 3, + "source_variable": { + "variable": "step_485_result_1", + "variable_path": "body.options" + } + }, + "resolver": { + "type": "continue" + }, + "variable": "step_555", + "next_step": "step_527" + }, + { + "step": "step_527", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Reading the file content and summarizing... This may take some time..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/summarize", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "variable": "step_555" + }, + "parameter": "file_index" + }, + { + "value": { + "skill_variable": "session_id" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_527_result_1" + } + }, + "variable": "step_527", + "next_step": "step_366" + }, + { + "step": "step_366", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Failed to summarize the content.\n\n\n\n" + }, + { + "variable": "step_527_result_1", + "variable_path": "body.message" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_366", + "condition": { + "eq": [ + { + "variable": "step_527_result_1", + "variable_path": "body.success" + }, + { + "scalar": false + } + ] + }, + "next_step": "step_827" + }, + { + "step": "step_827", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "" + }, + { + "variable": "step_527_result_1", + "variable_path": "body.summary" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_827", + "condition": { + "eq": [ + { + "variable": "step_527_result_1", + "variable_path": "body.success" + }, + { + "scalar": true + } + ] + } + } + ], + "title": "Summarize", + "action": "action_34313", + "boosts": [], + "handlers": [], + "condition": { + "intent": "action_34313_intent_10519" + }, + "variables": [ + { + "title": "Failed to summarize the content. {variable}", + "privacy": { + "enabled": false + }, + "variable": "step_366", + "data_type": "any" + }, + { + "title": "Checking the Watson Discovery processing status of uploaded file", + "privacy": { + "enabled": false + }, + "variable": "step_485", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_485_result_1", + "data_type": "any" + }, + { + "title": "Reading the file content and summarizing... This may take some t", + "privacy": { + "enabled": false + }, + "variable": "step_527", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_527_result_1", + "data_type": "any" + }, + { + "title": "Which file would you like me to summarize? Please select one of ", + "privacy": { + "enabled": false + }, + "variable": "step_555", + "data_type": "any" + }, + { + "title": "Please upload your docs before proceed.", + "privacy": { + "enabled": false + }, + "variable": "step_782", + "data_type": "any" + }, + { + "title": "{variable}", + "privacy": { + "enabled": false + }, + "variable": "step_827", + "data_type": "any" + } + ], + "launch_mode": "learning", + "next_action": "action_8715", + "topic_switch": { + "allowed_from": true, + "allowed_into": true, + "never_return": false + }, + "disambiguation_opt_out": false + }, + { + "steps": [ + { + "step": "step_485", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Checking the Watson Discovery processing status of uploaded files..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/docs_status", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "skill_variable": "session_id" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_485_result_1" + } + }, + "variable": "step_485", + "next_step": "step_782" + }, + { + "step": "step_782", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "The documents are still being processed by Watson Discovery, please wait..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_782", + "condition": { + "expression": "${step_485_result_1.body.options}.length == 0" + }, + "next_step": "step_555" + }, + { + "step": "step_555", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Which file would you like me to search? Please select one of the following files:" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + }, + { + "response_type": "response_from_variable", + "repeat_on_reprompt": true + } + ] + }, + "context": { + "variables": [ + { + "value": { + "variable": "step_485_result_1", + "variable_path": "body.session_id" + }, + "skill_variable": "session_id" + } + ] + }, + "handlers": [ + { + "type": "not_found", + "title": "validation_not_found_handler", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "I didn't catch that. Select a valid option:" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "incremental" + } + ] + }, + "handler": "validation_not_found_handler", + "resolver": { + "type": "prompt_again" + }, + "next_handler": "validation_not_found_max_tries_handler" + }, + { + "type": "not_found_max_tries", + "title": "validation_not_found_max_tries_handler", + "handler": "validation_not_found_max_tries_handler", + "resolver": { + "type": "fallback" + } + } + ], + "question": { + "max_tries": 3, + "source_variable": { + "variable": "step_485_result_1", + "variable_path": "body.options" + } + }, + "resolver": { + "type": "continue" + }, + "variable": "step_555", + "next_step": "step_959" + }, + { + "step": "step_959", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "What would you like to search?" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "question": { + "free_text": true + }, + "resolver": { + "type": "continue" + }, + "variable": "step_959", + "next_step": "step_527" + }, + { + "step": "step_527", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Searching in the file..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/search_in_doc", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "variable": "step_959" + }, + "parameter": "query" + }, + { + "value": { + "variable": "step_555" + }, + "parameter": "file_index" + }, + { + "value": { + "skill_variable": "session_id" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_527_result_1" + } + }, + "variable": "step_527", + "next_step": "step_366" + }, + { + "step": "step_366", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Failed to search in the content.\n\n\n\n" + }, + { + "variable": "step_527_result_1", + "variable_path": "body.message" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_366", + "condition": { + "eq": [ + { + "variable": "step_527_result_1", + "variable_path": "body.success" + }, + { + "scalar": false + } + ] + }, + "next_step": "step_827" + }, + { + "step": "step_827", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "" + }, + { + "variable": "step_527_result_1", + "variable_path": "body.message" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_827", + "condition": { + "eq": [ + { + "variable": "step_527_result_1", + "variable_path": "body.success" + }, + { + "scalar": true + } + ] + } + } + ], + "title": "Search in file", + "action": "action_34313-2", + "boosts": [], + "handlers": [], + "condition": { + "intent": "action_34313_intent_10519-2" + }, + "variables": [ + { + "title": "Failed to search in the content. {variable}", + "privacy": { + "enabled": false + }, + "variable": "step_366", + "data_type": "any" + }, + { + "title": "Checking the Watson Discovery processing status of uploaded file", + "privacy": { + "enabled": false + }, + "variable": "step_485", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_485_result_1", + "data_type": "any" + }, + { + "title": "Searching in the file...", + "privacy": { + "enabled": false + }, + "variable": "step_527", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_527_result_1", + "data_type": "any" + }, + { + "title": "Which file would you like me to search? Please select one of the", + "privacy": { + "enabled": false + }, + "variable": "step_555", + "data_type": "any" + }, + { + "title": "The documents are still being processed by Watson Discovery, ple", + "privacy": { + "enabled": false + }, + "variable": "step_782", + "data_type": "any" + }, + { + "title": "{variable}", + "privacy": { + "enabled": false + }, + "variable": "step_827", + "data_type": "any" + }, + { + "title": "What would you like to search?", + "privacy": { + "enabled": false + }, + "variable": "step_959", + "data_type": "any" + } + ], + "launch_mode": "learning", + "next_action": "fallback", + "topic_switch": { + "allowed_from": true, + "allowed_into": true, + "never_return": false + }, + "disambiguation_opt_out": false + }, + { + "steps": [ + { + "step": "step_288", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Would you like to clear all uploaded files in this conversation?" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential", + "repeat_on_reprompt": false + }, + { + "options": [ + { + "label": "Yes", + "value": { + "input": { + "text": "Yes" + } + } + }, + { + "label": "No", + "value": { + "input": { + "text": "No" + } + } + } + ], + "response_type": "option", + "repeat_on_reprompt": true + } + ] + }, + "handlers": [ + { + "type": "not_found", + "title": "validation_not_found_handler", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "I'm sorry, I did not catch that, please restate your response." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "incremental" + } + ] + }, + "handler": "validation_not_found_handler", + "resolver": { + "type": "prompt_again" + }, + "next_handler": "validation_not_found_max_tries_handler" + }, + { + "type": "not_found_max_tries", + "title": "validation_not_found_max_tries_handler", + "handler": "validation_not_found_max_tries_handler", + "resolver": { + "type": "fallback" + } + } + ], + "question": { + "entity": "sys-yes-no", + "max_tries": 3, + "response_collection_behavior": "always_ask" + }, + "resolver": { + "type": "continue" + }, + "variable": "step_288", + "next_step": "step_509" + }, + { + "step": "step_509", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Sure." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_509", + "condition": { + "eq": [ + { + "variable": "step_288" + }, + { + "scalar": "no" + } + ] + }, + "next_step": "step_136" + }, + { + "step": "step_136", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Cleanning up the uploaded docs..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/docs_status", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "scalar": "\"\"" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_136_result_1" + } + }, + "variable": "step_136", + "next_step": "step_800" + }, + { + "step": "step_800", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "All files cleared!" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "context": { + "variables": [ + { + "value": { + "variable": "step_136_result_1", + "variable_path": "body.session_id" + }, + "skill_variable": "session_id" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_800" + } + ], + "title": "Clear files", + "action": "action_9343", + "boosts": [], + "handlers": [], + "condition": { + "intent": "action_9343_intent_29442" + }, + "variables": [ + { + "title": "Cleanning up the uploaded docs...", + "privacy": { + "enabled": false + }, + "variable": "step_136", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_136_result_1", + "data_type": "any" + }, + { + "title": "Would you like to clear all uploaded files in this conversation?", + "privacy": { + "enabled": false + }, + "variable": "step_288", + "data_type": "yes_no" + }, + { + "title": "Sure.", + "privacy": { + "enabled": false + }, + "variable": "step_509", + "data_type": "any" + }, + { + "title": "All files cleared!", + "privacy": { + "enabled": false + }, + "variable": "step_800", + "data_type": "any" + } + ], + "launch_mode": "learning", + "next_action": "action_34313", + "topic_switch": { + "allowed_from": true, + "allowed_into": true, + "never_return": false + }, + "disambiguation_opt_out": false + }, + { + "steps": [ + { + "step": "step_676", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Checking the status of uploaded files..." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "callout", + "callout": { + "path": "/docs_status", + "type": "integration_interaction", + "method": "POST", + "internal": { + "spec_hash_id": "fec3dc1cb22113632fa9308fd83596079da2fa3bd41a73a711a7b5e41cc97a12", + "catalog_item_id": "7ab3f8e0-cf0d-4588-b4b3-c5f01db990f3" + }, + "request_mapping": { + "body": [ + { + "value": { + "skill_variable": "session_id" + }, + "parameter": "session_id" + } + ] + }, + "result_variable": "step_676_result_1" + } + }, + "variable": "step_676", + "next_step": "step_291" + }, + { + "step": "step_291", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Please upload your docs before proceed." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_291", + "condition": { + "expression": "${step_676_result_1.body.options}.length == 0" + }, + "next_step": "step_943" + }, + { + "step": "step_943", + "output": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "" + }, + { + "variable": "step_676_result_1", + "variable_path": "body.message" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "context": { + "variables": [ + { + "value": { + "variable": "step_676_result_1", + "variable_path": "body.session_id" + }, + "skill_variable": "session_id" + } + ] + }, + "handlers": [], + "resolver": { + "type": "continue" + }, + "variable": "step_943" + } + ], + "title": "Files Status", + "action": "action_8715", + "boosts": [], + "handlers": [], + "condition": { + "intent": "action_8715_intent_34668" + }, + "variables": [ + { + "title": "Please upload your docs before proceed.", + "privacy": { + "enabled": false + }, + "variable": "step_291", + "data_type": "any" + }, + { + "title": "Checking the status of uploaded files...", + "privacy": { + "enabled": false + }, + "variable": "step_676", + "data_type": "any" + }, + { + "privacy": { + "enabled": false + }, + "variable": "step_676_result_1", + "data_type": "any" + }, + { + "title": "{variable}", + "privacy": { + "enabled": false + }, + "variable": "step_943", + "data_type": "any" + } + ], + "launch_mode": "learning", + "next_action": "action_12706", + "topic_switch": { + "allowed_from": true, + "allowed_into": true, + "never_return": false + }, + "disambiguation_opt_out": false + }, + { + "steps": [ + { + "step": "step_001", + "output": { + "generic": [ + { + "values": [ + { + "text": "I'm afraid I don't understand. Please rephrase your question." + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "end_action" + }, + "variable": "step_001", + "condition": { + "lte": [ + { + "system_variable": "no_action_matches_count" + }, + { + "scalar": 3 + } + ] + }, + "next_step": "step_002" + }, + { + "step": "step_002", + "output": { + "generic": [ + { + "values": [ + { + "text": "" + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "handlers": [], + "resolver": { + "type": "invoke_another_action_and_end", + "invoke_action": { + "action": "fallback", + "policy": "default", + "parameters": null, + "result_variable": "step_002_result_1" + } + }, + "variable": "step_002", + "condition": { + "gt": [ + { + "system_variable": "no_action_matches_count" + }, + { + "scalar": 3 + } + ] + } + } + ], + "title": "No action matches", + "action": "anything_else", + "boosts": [], + "handlers": [], + "condition": { + "expression": "anything_else" + }, + "variables": [ + { + "title": "I am afraid I do not understand what you are asking, please re-p", + "variable": "step_001", + "data_type": "any" + }, + { + "variable": "step_002", + "data_type": "any" + }, + { + "variable": "step_002_result_1", + "data_type": "any" + } + ], + "disambiguation_opt_out": true + } + ], + "intents": [ + { + "intent": "fallback_connect_to_agent", + "examples": [ + { + "text": "Call agent" + }, + { + "text": "Agent help" + }, + { + "text": "I would like to speak to a human" + }, + { + "text": "Can I connect to an agent?" + }, + { + "text": "I would like to speak to someone" + } + ], + "description": "Please transfer me to an agent" + }, + { + "intent": "action_34313_intent_10519-2", + "examples": [ + { + "text": "search" + }, + { + "text": "search in file" + }, + { + "text": "search within" + } + ] + }, + { + "intent": "action_12706_intent_17003", + "examples": [ + { + "text": "Upload" + }, + { + "text": "upload files" + } + ] + }, + { + "intent": "action_34313_intent_10519", + "examples": [ + { + "text": "summarize" + } + ] + }, + { + "intent": "action_9343_intent_29442", + "examples": [ + { + "text": "Clear files" + } + ] + }, + { + "intent": "action_8715_intent_34668", + "examples": [ + { + "text": "Files Status" + }, + { + "text": "List files" + } + ] + } + ], + "entities": [ + { + "entity": "danger_words", + "values": [], + "fuzzy_match": false + }, + { + "entity": "profane_words", + "values": [], + "fuzzy_match": false + }, + { + "entity": "sys-yes-no", + "values": [] + } + ], + "metadata": { + "api_version": { + "major_version": "v2", + "minor_version": "2021-11-27" + }, + "skill": { + "counts": { + "actions": 9, + "intents": 6, + "entities": 3, + "data_types": 0, + "collections": 0, + "global_variables": 3 + } + } + }, + "variables": [ + { + "title": "attachments", + "privacy": { + "enabled": false + }, + "variable": "attachments", + "data_type": "any", + "description": "", + "initial_value": { + "scalar": "[]" + } + }, + { + "title": "integrations", + "privacy": { + "enabled": false + }, + "variable": "integrations", + "data_type": "any", + "description": "" + }, + { + "title": "session_id", + "privacy": { + "enabled": false + }, + "variable": "session_id", + "data_type": "string", + "description": "" + } + ], + "data_types": [], + "collections": [], + "counterexamples": [], + "system_settings": { + "variable": { + "format": { + "time": { + "pattern": "short" + }, + "currency": { + "fraction_digits": 2 + } + } + }, + "off_topic": { + "enabled": true + }, + "auto_learn": { + "apply": true + }, + "topic_switch": { + "enabled": true, + "messages": { + "enable_confirmation": true, + "confirmation_failure": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "I'm sorry, I did not catch that, please confirm." + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + } + ] + }, + "confirmation_request": { + "generic": [ + { + "values": [ + { + "text_expression": { + "concat": [ + { + "scalar": "Do you want to continue with the previous topic: " + }, + { + "system_variable": "digressed_from" + }, + { + "scalar": "?" + } + ] + } + } + ], + "response_type": "text", + "selection_policy": "sequential" + }, + { + "options": [ + { + "label": "Yes", + "value": { + "input": { + "text": "Yes" + } + } + }, + { + "label": "No", + "value": { + "input": { + "text": "No" + } + } + } + ], + "response_type": "option", + "repeat_on_reprompt": true + } + ] + } + }, + "max_tries": 3 + }, + "generative_ai": { + "slot_filling": { + "enabled": false + } + }, + "disambiguation": { + "prompt": "Did you mean:", + "enabled": true, + "randomize": true, + "max_suggestions": 5, + "suggestion_text_policy": "title", + "none_of_the_above_prompt": "None of the above", + "use_connect_to_support_prompt": "Connect to support", + "single_answer_clarification_prompt": "Something else" + }, + "spelling_auto_correct": true + }, + "learning_opt_out": false, + "language": "en" + }, + "description": "created for assistant dd01d9b5-04c9-4dc2-88f9-f3f12ee6b13d", + "dialog_settings": { + "source_assistant": "17aeb863-dc67-49e9-aaa1-b686c886a9e6" + }, + "created": "2024-01-08T08:45:22.980Z", + "updated": "2024-01-08T08:45:22.980Z", + "snapshot": "3", + "assistant_id": "dd01d9b5-04c9-4dc2-88f9-f3f12ee6b13d", + "assistant_references": [ + { + "name": "1204 Demo", + "assistant_id": "17aeb863-dc67-49e9-aaa1-b686c886a9e6", + "skill_reference": "actions skill" + } + ] +} \ No newline at end of file diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/assets/enable_bot_file_upload.png b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/enable_bot_file_upload.png new file mode 100644 index 00000000..d2478206 Binary files /dev/null and b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/enable_bot_file_upload.png differ diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/assets/file-status.png b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/file-status.png new file mode 100644 index 00000000..caf4cde3 Binary files /dev/null and b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/file-status.png differ diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/assets/search.png b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/search.png new file mode 100644 index 00000000..f29c1d3c Binary files /dev/null and b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/search.png differ diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/assets/summarize.png b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/summarize.png new file mode 100644 index 00000000..5a4d3ea0 Binary files /dev/null and b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/summarize.png differ diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/assets/upload-1.png b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/upload-1.png new file mode 100644 index 00000000..aae71881 Binary files /dev/null and b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/upload-1.png differ diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/assets/upload-2.png b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/upload-2.png new file mode 100644 index 00000000..84a8e0f5 Binary files /dev/null and b/integrations/extensions/starter-kits/ms-teams-file-upload/assets/upload-2.png differ diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/openapi.json b/integrations/extensions/starter-kits/ms-teams-file-upload/openapi.json new file mode 100644 index 00000000..8886bea7 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/openapi.json @@ -0,0 +1,414 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "The WA2WD Upload Service API", + "version": "1.0.0", + "description": "", + "contact": {} + }, + "servers": [ + { + "url": "https://{host}:{port}", + "description": "WA2WD API server", + "variables": { + "host": { + "default": "", + "description": "Hostname of the API server" + }, + "port": { + "default": "443", + "description": "Port of the API server" + } + } + }, + { + "url": "https://{ngrok_id}.ngrok.app", + "description": "WA2WD API server (via ngrok)", + "variables": { + "ngrok_id": { + "default": "", + "description": "ngrok url prefix" + } + } + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer" + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "paths": { + "/upload": { + "post": { + "tags": [ + "API" + ], + "summary": "Upload to WD", + "description": "Upload document to Watson Discovery", + "operationId": "upload", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL of the uploaded file" + }, + "media_type": { + "type": "string", + "description": "MIME type of the uploaded file" + } + } + } + }, + "session_id": { + "type": "string", + "description": "Session ID" + } + }, + "required": [ + "attachments" + ] + } + } + } + }, + "responses": { + "200": { + "description": "success response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "session_id": { + "type": "string", + "example": "abcd-1234-efgh-5678" + }, + "message": { + "type": "string", + "example": "Files uploaded successfully." + } + } + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Authentication required." + } + } + } + } + } + } + } + } + }, + "/docs_status": { + "post": { + "tags": [ + "API" + ], + "summary": "List documents", + "description": "List documents uploaded", + "operationId": "docs_status", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "description": "Session ID" + } + }, + "required": [ + "session_id" + ] + } + } + } + }, + "responses": { + "200": { + "description": "success response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "documents": { + "type": "object", + "properties": { + "document_id": { + "type": "object", + "properties": { + "file_index": { + "type": "integer", + "example": 1 + }, + "file_name": { + "type": "string", + "example": "sample.pdf" + }, + "wd_status": { + "type": "string", + "example": "pending" + } + } + } + } + }, + "session_id": { + "type": "string", + "example": "abcd-1234-efgh-5678" + }, + "message": { + "type": "string", + "example": "Here are the files available in our conversation:..." + }, + "options": { + "type": "array", + "items": { + "type": "string", + "example": "1. sample.pdf" + } + } + } + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Authentication required." + } + } + } + } + } + } + } + } + }, + "/summarize": { + "post": { + "tags": [ + "API" + ], + "summary": "Summarize file content", + "description": "Summarize file content", + "operationId": "summarize", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "file_index": { + "type": "string", + "description": "URL of the uploaded file" + }, + "session_id": { + "type": "string", + "description": "Session ID" + } + }, + "required": [ + "file_index", + "session_id" + ] + } + } + } + }, + "responses": { + "200": { + "description": "success response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "session_id": { + "type": "string", + "example": "abcd-1234-efgh-5678" + }, + "summary": { + "type": "string", + "example": "This is a summary of the document." + } + } + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Authentication required." + } + } + } + } + } + } + } + } + }, + "/search_in_doc": { + "post": { + "tags": [ + "API" + ], + "summary": "Search in document", + "description": "Search in document", + "operationId": "summarize", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "file_index": { + "type": "string", + "description": "URL of the uploaded file" + }, + "query": { + "type": "string", + "description": "Query to search" + }, + "session_id": { + "type": "string", + "description": "Session ID" + } + }, + "required": [ + "file_index", + "query", + "session_id" + ] + } + } + } + }, + "responses": { + "200": { + "description": "success response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "session_id": { + "type": "string", + "example": "abcd-1234-efgh-5678" + }, + "results": { + "type": "array", + "items": { + "type": "string", + "example": "This is a result of the search." + } + }, + "message": { + "type": "string", + "example": "I found the following results for your query:..." + } + } + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Authentication required." + } + } + } + } + } + } + } + } + } + }, + "tags": [ + { + "name": "API" + } + ] +} \ No newline at end of file diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/readme.md b/integrations/extensions/starter-kits/ms-teams-file-upload/readme.md new file mode 100644 index 00000000..d0535187 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/readme.md @@ -0,0 +1,174 @@ +# MS Teams File Upload Starter Kit + +> When using this starter kit to create assistant instances, please make sure to **first create and add the extensions +to +> the assistant, then upload the actions.** Therefore, the actions can be automatically associated with the extension. + +## Goal + +This is a simple example of how to enable file upload in Watson Assistant with **MS Teams** channel, and +forward the file to other services via custom extension for further processing. + +This starter kit implements the following features: + +- File uploading via Teams to watsonx Assistant and read file's metadata and content in the assistant actions. +- Forward the file to Watson Discovery to perform custom in-file search. +- Summarize the file content with watsonx.ai foundation models. + +> The file summarization feature only supports PDF files in English. + +## Preparation + +Please note that you only need to follow the steps below if you want to try the in-file search and content summarization +feature. + +If you are not interested in testing these features, you can skip this section. The two features +can be disabled by setting the environment variable `WD_ENABLED` and `WATSONX_ENABLED` to `false` respectively. + +### In-file search with Watson Discovery + +If you would like to try the in-file search feature, you need to +[create a Watson Discovery instance](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-getting-started&locale=zh-CN#before-you-begin-tool), +then [create a project](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-projects) +and [a collection](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-collections). + +Please follow [this guide](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-api-use) to +prepare the following info: + +- `WD_API_URL`: The API URL of the Watson Discovery instance. +- `WD_APIKEY`: The API key of the Watson Discovery instance. +- `WD_PROJECT_ID`: The project ID of the project you created in the Watson Discovery instance +- `WD_COLLECTION_ID`: The collection ID of the collection you created in the project above + +### Content summarization with watsonx.ai + +If you would like to try the summarization feature, you need to create a watsonx.ai project and obtain the following +info: + +- `WATSONX_API_URL`: The API URL of the watsonx.ai instance. depends on the region of the + instance. [See here](https://ibm.github.io/watson-machine-learning-sdk/setup_cloud.html#authentication) for possible + values. +- `WATSONX_API_KEY`: The API key of the watsonx.ai instance. + Follow [this guide](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-credentials.html?context=wx&audience=wdp) + to find it. +- `WATSONX_PROJECT_ID`: The project ID of the watsonx.ai project that you want to use to call the LLM models. + Follow [this guide](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-python-lib.html?context=wx&audience=wdp#project-id) + to find it. + +## Setup Steps + +1. [Deploy the watsonx Assistant Teams File Upload API Server](#deploy-the-watsonx-assistant-teams-file-upload-api-server) +2. Upload the custom extension and sample actions to watsonx Assistant +3. Integrate the watsonx Assistant with Microsoft Teams + - Create Teams app with **file upload** feature enabled + +## Deploy the watsonx Assistant Teams File Upload API Server + +The custom extension requires an API server to fetch the uploaded file from Teams and forward it to other services. The +API server can be deployed locally or on the IBM Cloud Code Engine. + +### Local Deployment + +The server was developed with Python flask framework. To deploy the server locally, please follow the steps below: + +1. Install Python 3.8 or above +2. Clone the repo and navigate to the `teams-file-upload/server` folder +3. Create a virtual environment with `python3 -m venv venv`, and activate it with `source venv/bin/activate` +4. Install the dependencies with `pip3 install -r requirements.txt` +5. Copy the `.env-sample` file to `.env` and fill in the required information +6. Start the server with `python3 app.py` +7. The server will be running on `http://localhost:5050` by default. You should see "Teams File Upload API server is up + and running." if open the link in browser. + +To allow Watson Assistant to access the API server, you need to expose the server to the internet. You can use tools +like +[ngrok](https://ngrok.com/) to do that. After installing ngrok, run `ngrok http 5050` to expose the server to the +internet. + +### IBM Cloud Code Engine Deployment + +The server comes with the Dockerfile for Code Engine deployment. To deploy the server to Code Engine, please follow the +steps below: + +1. Build the docker image + 1. Copy the `.env-sample` file to `.env` and fill in the required information + 2. Build the image with `docker build -t .` The image name should follow the format + of `//:`. For example, `us.icr.io/tfu_ns/tfu_api_server:latest`. +2. Push the image to the container registry with `docker push ` +3. Create a Code Engine project + and [deploy the image](https://cloud.ibm.com/docs/codeengine?topic=codeengine-deploy-app-crimage) + +## Upload the custom extension and sample actions to watsonx Assistant + +### Custom Extension Setup + +1. [Upload the extension](https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-build-custom-extension). + The `openapi.json` provided is for creating the extension. +2. [Add the extension to the assistant](https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-add-custom-extension). + 1. In Authentication step, select `Bearer auth` as `Authentication type`, and fill the `Token` with the value of the + environment variable `API_SERVER_KEY` that configured in `.env` file of the API server. + 2. In Server variables section, enter the public URL of the API server and the port if needed. + +### Upload Sample Actions + +> Uploading actions will override all existing actions in the assistant. + +We provided the following sample actions to demonstrate how you can access the uploaded file in the WA actions. + +| WA Action Name | WA Trigger Word | Description | +|----------------------|-----------------|------------------------------------------------------------------------------------------------------------| +| Upload to WD | "upload" | Upload or append one or more files to current session. | +| Check File Status | "file status" | Check the info of the uploaded files in current session, and also their process status on Watson Discovery | +| Summarize | "summarize" | Summarize the file content with watsonx.ai foundation models. | +| Search | "search" | Search the file content with Watson Discovery. | +| Clear Uploaded Files | "clear" | Clear the uploaded files by create a new session. | + +To upload the actions: + +1. Zip `action-skill.json` into a zip file `action-skill.zip` for uploading. +2. [Uploading all actions](https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-upload-download-actions) + +## Integrate the watsonx Assistant with Microsoft Teams + +Please follow this guide +to [integrate watsonx Assistant with MS Teams](https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-deploy-microsoft-teams). + +In order to support file uploading, you need to create a Teams app with **file upload** feature enabled. + +When following the guide above, +in [Create your Teams app](https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-deploy-microsoft-teams#deploy-microsoft-teams-app) +section, before saving the bot configuration (step 8), you need to also enable the **Upload and download files** feature +in the **What can your bot do?** section. + +![Enable file upload](./assets/enable_bot_file_upload.png) + +## Example Usage + +### Upload files + +First type "upload" in the chat window, then upload a file. The file's info will be displayed in the chat window. +![Upload file](./assets/upload-1.png) + +You can type "upload" again to upload more files. The uploaded files will be appended to the current session. +![Upload file](./assets/upload-2.png) + +### Check file status + +Type "file status" in the chat window, and the file's info and process status will be displayed in the chat window. +Note that files has been processed by WD (with status `available`) and is now available for search. + +![Check file status](./assets/file-status.png) + +### Summarize + +Type "summarize" in the chat window, select the file you want to summarize, and the summary will be displayed in the +chat window after a few seconds. + +![Summarize](./assets/summarize.png) + +### Search + +Type "search" in the chat window, select the file you want to search, and then enter your search query. The search +result will be displayed in the chat window after a few seconds. + +![Search](./assets/search.png) \ No newline at end of file diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/.env-sample b/integrations/extensions/starter-kits/ms-teams-file-upload/server/.env-sample new file mode 100644 index 00000000..8b97d4c5 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/.env-sample @@ -0,0 +1,18 @@ +# API Server config +API_SERVER_PORT=5050 +API_SERVER_KEY=api-key-for-this-server + +# Watson Discovery config +WD_ENABLED=true +WD_API_URL=https://api.us-south.discovery.watson.cloud.ibm.com/instances/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +WD_API_VERSION=2023-03-31 +WD_APIKEY=XXXXXXXXXXXXXXXXXXXXXXX-XXXXXXX +WD_PROJECT_ID=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +WD_COLLECTION_ID=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + +# WatsonX config +WATSONX_ENABLED=true +WATSONX_API_URL=https://us-south.ml.cloud.ibm.com +WATSONX_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +WATSONX_PROJECT_ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +# More options available inside watsonx_api.py diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/.gitignore b/integrations/extensions/starter-kits/ms-teams-file-upload/server/.gitignore new file mode 100644 index 00000000..1b5c3a24 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/.gitignore @@ -0,0 +1,185 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject +### VirtualEnv template +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +.venv +pip-selfcheck.json + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# idea folder, uncomment if you don't need it +.idea \ No newline at end of file diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/Dockerfile b/integrations/extensions/starter-kits/ms-teams-file-upload/server/Dockerfile new file mode 100644 index 00000000..1ae007ab --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.10-slim-buster + +WORKDIR /python-docker + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY . . +COPY .env .env + +EXPOSE 5050 + +CMD [ "python3", "app.py" ] \ No newline at end of file diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/app.py b/integrations/extensions/starter-kits/ms-teams-file-upload/server/app.py new file mode 100644 index 00000000..16658d24 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/app.py @@ -0,0 +1,222 @@ +import os + +from flask import Flask, request +from dotenv import load_dotenv + +from document import Document +from session import Session +from utils import determine_option_index, format_search_result_text + +load_dotenv() # take environment variables from .env + +app = Flask(__name__) + +WD_ENABLED = os.getenv('WD_ENABLED', 'true').lower() == 'true' +WATSONX_ENABLED = os.getenv('WATSONX_ENABLED', 'true').lower() == 'true' + + +@app.before_request +def retrieve_session_data(): + """ + Retrieve session data from request by reading the session_id parameter. If the session_id is not present or invalid, + a new session will be created. + The session object will be added as a property into the request object, which can be accessed with `request.session` + in route functions. + :return: + """ + + # Do not check session for the status route + if request.path == '/': + return + + data = request.get_json() + if data is None or 'session_id' not in data: + request.session = Session.new_session() + else: + request.session = Session.get_session(data['session_id']) + + +@app.before_request +def authenticate(): + """ + Check if the request has correct Bearer token. The API key is set by the API_SERVER_KEY environment variable. + :return: + """ + + # Do not check authentication for the status route + if request.path == '/': + return + + token = request.headers.get('Authorization', '').strip() + if token != f'Bearer {os.getenv("API_SERVER_KEY")}'.strip(): + return {'success': False, 'message': 'Authentication required.'}, 401 + + +@app.route('/') +def status(): + return 'Teams File Upload API server is up and running.' + + +@app.post('/upload') +def upload(): + """ + Retrieve the document from the URL provided by Teams, store its data into current session object and upload it to + Watson Discovery. The file's text will be extracted and stored in the session object as well. + :return: + """ + try: + data = request.get_json() + + attachments = data.get('attachments') + + if None in (attachments,): + return { + 'success': False, + 'message': 'Missing required parameters. Required parameters: attachments' + }, 400 + + print(f"Uploading {len(attachments)} documents...") + for file in attachments: + file_url = file['url'] + mine_type = file['media_type'] + + document = Document.from_url(file_url, mine_type) + if WD_ENABLED: + document.upload_to_wd() + document.extract_text() + + print(f"Document {document.file_name} uploaded successfully.") + + request.session.add_document(document) + except Exception as e: + return {'success': False, 'message': str(e)}, 500 + + return { + 'success': True, + 'session_id': request.session.id, + 'message': f"Documents has been uploaded successfully.", + } + + +@app.post('/docs_status') +def docs_status(): + """ + Retrieve the status of all documents in current session. + :return: + """ + + try: + return { + 'success': True, + 'session_id': request.session.id, + # list of dict that contains file info, not really used + 'documents': request.session.get_all_documents_status(), + # list of file names in format "1. file_name", used to generate WA dynamic options + 'options': request.session.get_all_documents_options(), + # HTML table that contains file info, rendered in Teams chat + 'message': f'Here are the files available in our conversation:\n{request.session.get_all_documents_status_table()}' + } + + except Exception as e: + return {'success': False, 'message': str(e)}, 500 + + +@app.post('/summarize') +def summarize(): + """ + Summarize the document with the given index with WatsonX foundation model. + :return: + """ + + try: + if not WATSONX_ENABLED: + raise ValueError("WatsonX feature is not enabled.") + + data = request.get_json() + index = determine_option_index(data.get('file_index')) + if index is None: + return { + 'success': False, + 'message': 'Missing required parameters. Required parameters: file_index' + }, 400 + + doc = request.session.get_doc_by_index(index) + + return { + 'success': True, + 'session_id': request.session.id, + 'summary': doc.summarize() + } + except Exception as e: + return {'success': False, 'message': str(e)}, 500 + + +@app.post('/search_in_doc') +def search_in_doc(): + """ + Search within specific document for given query with Watson Discovery. + :return: + """ + + try: + if not WD_ENABLED: + raise ValueError("Watson Discovery feature is not enabled.") + + data = request.get_json() + index = determine_option_index(data.get('file_index')) + query = data.get('query') + + if None in (index, query): + return { + 'success': False, + 'message': 'Missing required parameters. Required parameters: file_index, query' + }, 400 + + doc = request.session.get_doc_by_index(index) + + search_results = doc.search_in_doc(query) + + has_results = len(search_results) > 0 + + filename = doc.file_name + if doc.url: + filename = f"{filename}" + + if not has_results: + return { + 'success': True, + 'session_id': request.session.id, + 'results': search_results, + 'message': f"I found no result for your query '{query}' in document {filename}." + } + else: + # Display the search result in the following format: + # I found 2 result(s) about 'query' in document 'filename': + # |---|-----------------------------| + # | 1 | result 1 blablabla | + # |---|-----------------------------| + # | 2 | result 2 blablabla | + # |---|-----------------------------| + + message = f"I found {len(search_results)} result(s) about '{query}' in document {filename}:\n" + + message += "" + + for i, result in enumerate(search_results): + message += f"" + + message += "
{i + 1}{format_search_result_text(result)}
" + + return { + 'success': True, + 'session_id': request.session.id, + 'results': search_results, + 'message': message + } + + except Exception as e: + return {'success': False, 'message': str(e)}, 500 + + +if __name__ == '__main__': + app.run(port=os.getenv('API_SERVER_PORT', 5000), host='0.0.0.0', debug=True) diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/document.py b/integrations/extensions/starter-kits/ms-teams-file-upload/server/document.py new file mode 100644 index 00000000..ce57032a --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/document.py @@ -0,0 +1,100 @@ +from io import BytesIO +from typing import List + +import requests + +from utils import determine_file_name +from watsonx_api import summarize +from wd_api import add_document, check_status, search_in_doc + +from PyPDF2 import PdfReader + + +class Document: + def __init__(self, file_name: str, binary_data: bytes, mine_type: str = None, text: str = None, url: str = None): + self.url = url + self.file_name = file_name + self.mine_type = mine_type + self.binary_data = binary_data + self.text = text + self.wd_id = None + self.wd_status = 'not uploaded' + + def upload_to_wd(self) -> dict: + response = add_document(BytesIO(self.binary_data), self.file_name, self.mine_type) + self.wd_id = response['document_id'] + self.wd_status = response['status'] + return response + + def check_wd_status(self, force: bool = False) -> dict: + if self.wd_id is None: + return {'status': 'not uploaded'} + + if not force and self.wd_status == 'available': + return {'status': 'available'} + + response = check_status(self.wd_id) + self.wd_status = response['status'] + return response['status'] + + @classmethod + def from_url(cls, url: str, mine_type: str) -> 'Document': + # Download file as binaryIO + file_response = requests.get(url) + file_data = file_response.content + + # The filename is determined by reading the Content-Disposition header of the file response. + # If the header is not present, a random name is generated. + file_name = determine_file_name(file_response) + + if file_response.status_code != 200: + raise ValueError(f"Error downloading file from {url}. Server response code: {file_response.status_code}") + + print(f"File downloaded, name: {file_name}; size: {int(file_response.headers['Content-Length']) // 1024} KB") + + return cls(file_name, file_data, mine_type, None, url) + + def extract_text(self) -> str: + if 'pdf' not in self.mine_type: + raise ValueError(f"File type {self.mine_type} not supported.") + + texts = [] + reader = PdfReader(BytesIO(self.binary_data)) + + for page in reader.pages: + texts.append(page.extract_text()) + + self.text = '\n\n'.join(texts) + return self.text + + def summarize(self) -> str: + if self.text is None: + raise ValueError("Document text not extracted.") + + return summarize(self.text) + + def search_in_doc(self, query: str) -> List[str]: + self.check_wd_status() + + if self.wd_status != 'available': + raise ValueError(f"Document {self.file_name} is not available on WD yet. Current status: {self.wd_status}") + + response = search_in_doc(self.wd_id, query) + + results = [] + + for result in response['results']: + for document_passage in result['document_passages']: + results.append(document_passage['passage_text']) + + return results + + def get_size_string(self) -> str: + size = len(self.binary_data) + + if size < 1024: + return f"{size} B" + elif size < 1024 ** 2: + return f"{size // 1024} KB" + elif size < 1024 ** 3: + return f"{size // 1024 ** 2} MB" diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/requirements.txt b/integrations/extensions/starter-kits/ms-teams-file-upload/server/requirements.txt new file mode 100644 index 00000000..1764aebc --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/requirements.txt @@ -0,0 +1,5 @@ +flask==3.0.0 +python-dotenv==1.0.0 +ibm-watson==7.0.0 +ibm-watson-machine-learning==1.0.335 +PyPDF2==3.0.1 \ No newline at end of file diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/session.py b/integrations/extensions/starter-kits/ms-teams-file-upload/server/session.py new file mode 100644 index 00000000..b9f80a0b --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/session.py @@ -0,0 +1,70 @@ +import uuid +from typing import List + +from document import Document + +sessions = {} + + +class Session: + """ + A session object stores all the documents uploaded by the user in a conversation. + """ + def __init__(self): + self.id = str(uuid.uuid4()) + self.documents: List[Document] = [] + + @classmethod + def new_session(cls) -> 'Session': + session = cls() + sessions[session.id] = session + return session + + @classmethod + def get_session(cls, session_id: str) -> 'Session': + return sessions.get(session_id, cls.new_session()) + + def add_document(self, document: Document): + self.documents.append(document) + + def get_all_documents_status(self) -> list[dict]: + for doc in self.documents: + doc.check_wd_status() + + return [{"file_index": i + 1, "file_name": doc.file_name, "wd_status": doc.wd_status} for i, doc in + enumerate(self.documents)] + + def get_all_documents_options(self) -> list[str]: + for doc in self.documents: + doc.check_wd_status() + + return [f"{i + 1}. {doc.file_name} ({doc.wd_status})" for i, doc in enumerate(self.documents)] + + def get_all_documents_status_table(self, available_on_wd: bool = False) -> str: + result = "" + + for i, doc in enumerate(self.documents): + doc.check_wd_status() + + if available_on_wd and doc.wd_status != 'available': + continue + + filename = doc.file_name + if doc.url: + filename = f"{filename}" + + result += f"" + + result += "
IndexFile nameMime typeSizeWD processing status
{i + 1}{filename}{doc.mine_type}{doc.get_size_string()}{doc.wd_status}
" + + return result + + def get_doc_by_index(self, index: int) -> Document: + if len(self.documents) == 0: + raise ValueError("No documents uploaded.") + elif index >= len(self.documents): + raise ValueError(f"Invalid document index {index}. There are {len(self.documents)} documents uploaded.") + elif index < 0: + raise ValueError(f"Invalid document index {index}. Index must be greater than or equal to 0.") + + return self.documents[index] diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/utils.py b/integrations/extensions/starter-kits/ms-teams-file-upload/server/utils.py new file mode 100644 index 00000000..83b730e3 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/utils.py @@ -0,0 +1,77 @@ +import random +from datetime import datetime +from urllib.parse import unquote + +from typing import Optional + +import requests + + +def determine_file_name(response: requests.Response) -> str: + name = '' + if 'Content-Disposition' in response.headers: + name = extract_file_name(response) + + if not name: + name = generate_file_name() + + return name + + +def generate_file_name() -> str: + # generate file name with date, time and random string + return f"file_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{''.join(random.choices('0123456789abcdef', k=8))}" + + +def extract_file_name(response: requests.Response) -> Optional[str]: + header = response.headers['Content-Disposition'] + filename = '' + for item in header.split(';'): + if '=' in item: + k, v = item.split('=') + if k.strip() == 'filename*': + filename = v[7:] + break + elif k.strip() == 'filename': + filename = v.strip('"') + break + + return unquote(filename) + + +def tokenize(text: str) -> list[str]: + return text.split() + + +def count_tokens(text: str) -> int: + return len(tokenize(text)) + + +def split_into_paragraphs(text: str, max_tokens: int = 2048) -> list[str]: + paragraphs = [] + tokens = tokenize(text) + + while len(tokens) > max_tokens: + paragraph = ' '.join(tokens[:max_tokens]) + paragraphs.append(paragraph) + tokens = tokens[max_tokens:] + + paragraphs.append(' '.join(tokens)) + + return paragraphs + + +def determine_option_index(option_text: str) -> int: + # e.g. option_text = "1. Option ABC" -> return 0 + try: + return int(option_text.split('.')[0]) - 1 + except ValueError: + raise ValueError(f"Unable to determine option index from text: {option_text}") + + +def extract_last_paragraph(text: str) -> str: + return text.split('\n')[-1] + + +def format_search_result_text(text: str) -> str: + return text.replace('', '').replace('', '') diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/watsonx_api.py b/integrations/extensions/starter-kits/ms-teams-file-upload/server/watsonx_api.py new file mode 100644 index 00000000..9140cce8 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/watsonx_api.py @@ -0,0 +1,97 @@ +import os +import threading + +from dotenv import load_dotenv + +from utils import split_into_paragraphs, count_tokens, extract_last_paragraph + +from ibm_watson_machine_learning.foundation_models import Model +from ibm_watson_machine_learning.metanames import GenTextParamsMetaNames as GenParams +from ibm_watson_machine_learning.foundation_models.utils.enums import ModelTypes + + +load_dotenv() # take environment variables from .env + +# The model used for summarization +MODEL_ID = ModelTypes.LLAMA_2_70B_CHAT +# The maximum number of tokens that can be passed to the model. If the input text is longer than this, it will be split +# into multiple paragraphs and each paragraph will be summarized separately. +MAX_INPUT_TOKENS = 2048 +# The minimum and maximum number of tokens that will be generated by the model. +MIN_NEW_TOKENS = 50 +MAX_NEW_TOKENS = 500 +# The prompt that will be prepended to the input text before summarization. +SUMMARIZATION_PROMPT = ("Please summarize the following text into a single paragraph with 3-4 sentences. " + "Output complete sentences. " + "Use professional language. " + "Output only the summarized result, do not repeat the prompt. " + "Here is the text:") + +# To display example params enter +GenParams().get_example_values() + +generate_params = { + GenParams.MIN_NEW_TOKENS: MIN_NEW_TOKENS, + GenParams.MAX_NEW_TOKENS: MAX_NEW_TOKENS, +} + +model = Model( + model_id=MODEL_ID, + params=generate_params, + credentials={ + "apikey": os.getenv('WATSONX_API_KEY'), + "url": os.getenv('WATSONX_API_URL') + }, + project_id=os.getenv('WATSONX_PROJECT_ID') +) + + +def generate(prompt: str) -> str: + generated_response = model.generate(prompt=prompt) + return generated_response['results'][0]['generated_text'] + + +def _summarize(text: str) -> str: + return extract_last_paragraph(generate(SUMMARIZATION_PROMPT + "\n\n" + text)) + + +def summarize(text: str, max_input_tokens: int = MAX_INPUT_TOKENS) -> str: + print(f'Summarizing {count_tokens(text)} tokens') + + paragraphs = split_into_paragraphs(text, max_input_tokens) + + if len(paragraphs) == 1: + print('Only one paragraph, summarizing it directly') + summary = _summarize(paragraphs[0]) + print(f'> {summary}') + return summary + + print(f'Split into {len(paragraphs)} paragraphs') + paragraph_summaries = [""] * len(paragraphs) + threads = [] + + for i, paragraph in enumerate(paragraphs): + def target(i, paragraph): + print(f'Started summarization thread for paragraph {i}') + paragraph_summaries[i] = _summarize(paragraph) + print(f'Finished summarization thread for paragraph {i}') + print(f'> {paragraph_summaries[i]}') + + t = threading.Thread(target=target, args=(i, paragraph)) + threads.append(t) + t.start() + + for t in threads: + t.join() + + paragraph_summary = '\n\n'.join(paragraph_summaries) + + print('Combining paragraph summaries') + summary = summarize(paragraph_summary) + print(f'> {summary}') + + if len(summary) > len(paragraph_summary): + print('Summary is longer than the paragraph_summary text, returning paragraph_summary') + return paragraph_summary + + return summary diff --git a/integrations/extensions/starter-kits/ms-teams-file-upload/server/wd_api.py b/integrations/extensions/starter-kits/ms-teams-file-upload/server/wd_api.py new file mode 100644 index 00000000..4c997142 --- /dev/null +++ b/integrations/extensions/starter-kits/ms-teams-file-upload/server/wd_api.py @@ -0,0 +1,50 @@ +import os +from typing import BinaryIO + +from ibm_watson import DiscoveryV2 +from ibm_cloud_sdk_core.authenticators import BasicAuthenticator + +from dotenv import load_dotenv + +load_dotenv() # take environment variables from .env + +authenticator = BasicAuthenticator('apikey', os.getenv('WD_APIKEY')) +discovery = DiscoveryV2( + version=os.getenv('WD_API_VERSION', '2023-03-31'), + authenticator=authenticator +) +discovery.set_service_url(os.getenv('WD_API_URL')) + + +def add_document(f: BinaryIO, file_name: str, + file_content_type: str = None, project_id: str = os.getenv('WD_PROJECT_ID'), + collection_id: str = os.getenv('WD_COLLECTION_ID')) -> dict: + response = discovery.add_document( + project_id=project_id, + collection_id=collection_id, + filename=file_name, + file_content_type=file_content_type, + file=f, + ).get_result() + return response + + +def check_status(document_id: str, project_id: str = os.getenv('WD_PROJECT_ID'), + collection_id: str = os.getenv('WD_COLLECTION_ID')) -> dict: + response = discovery.get_document( + project_id=project_id, + collection_id=collection_id, + document_id=document_id, + ).get_result() + return response + + +def search_in_doc(document_id: str, query: str, project_id: str = os.getenv('WD_PROJECT_ID'), + collection_id: str = os.getenv('WD_COLLECTION_ID')) -> dict: + response = discovery.query( + project_id=project_id, + collection_ids=[collection_id], + natural_language_query=query, + filter=f"document_id::{document_id}", + ).get_result() + return response