mirror of
https://github.com/hwchase17/langchain.git
synced 2025-06-30 10:23:30 +00:00
Add ClickUp Toolkit (#10662)
- **Description:** Adds a toolkit to interact with the [ClickUp](https://clickup.com/) [Public API](https://clickup.com/api/) - **Dependencies:** None - **Tag maintainer:** @rodrigo-georgian, @rodrigo-clickup, @aiswaryasankarwork - **Twitter handle:** - Aiswarya (https://twitter.com/Aiswarya_Sankar, https://www.linkedin.com/in/sankaraiswarya/) - Rodrigo (https://www.linkedin.com/in/rodrigo-ceballos-lentini/) --------- Co-authored-by: Aiswarya Sankar <aiswaryasankar@Aiswaryas-MacBook-Pro.local> Co-authored-by: aiswaryasankarwork <143119412+aiswaryasankarwork@users.noreply.github.com> Co-authored-by: Bagatur <baskaryan@gmail.com>
This commit is contained in:
parent
68901e1e40
commit
5944c1851b
855
docs/extras/integrations/toolkits/clickup.ipynb
Normal file
855
docs/extras/integrations/toolkits/clickup.ipynb
Normal file
@ -0,0 +1,855 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# ClickUp Langchiain Toolkit"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"%reload_ext autoreload\n",
|
||||||
|
"%autoreload 2\n",
|
||||||
|
"from datetime import datetime\n",
|
||||||
|
"\n",
|
||||||
|
"from langchain.agents.agent_toolkits.clickup.toolkit import ClickupToolkit\n",
|
||||||
|
"\n",
|
||||||
|
"from langchain.agents import AgentType, initialize_agent\n",
|
||||||
|
"from langchain.llms import OpenAI\n",
|
||||||
|
"from langchain.utilities.clickup import ClickupAPIWrapper\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Init"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Get Authenticated\n",
|
||||||
|
"1. Create a [ClickUp App](https://help.clickup.com/hc/en-us/articles/6303422883095-Create-your-own-app-with-the-ClickUp-API)\n",
|
||||||
|
"2. Follow [these steps](https://clickup.com/api/developer-portal/authentication/) to get your `client_id` and `client_secret`.\n",
|
||||||
|
" - *Suggestion: use `https://google.com` as the redirect_uri. This is what we assume in the defaults for this toolkit.*\n",
|
||||||
|
"3. Copy/paste them and run the next cell to get your `code`\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 48,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Click this link, select your workspace, click `Connect Workspace`\n",
|
||||||
|
"https://app.clickup.com/api?client_id=ABC...&redirect_uri=https://google.com\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Copilot Sandbox\n",
|
||||||
|
"oauth_client_id = \"ABC...\"\n",
|
||||||
|
"oauth_client_secret = \"123...\"\n",
|
||||||
|
"redirect_uri = \"https://google.com\"\n",
|
||||||
|
"\n",
|
||||||
|
"print('Click this link, select your workspace, click `Connect Workspace`')\n",
|
||||||
|
"print(ClickupAPIWrapper.get_access_code_url(oauth_client_id, redirect_uri))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"The url should change to something like this https://www.google.com/?code=THISISMYCODERIGHTHERE.\n",
|
||||||
|
"\n",
|
||||||
|
"Next, copy/paste the `CODE` (THISISMYCODERIGHTHERE) generated in the URL in the cell below.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"code = \"THISISMYCODERIGHTHERE\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Get Access Token\n",
|
||||||
|
"Then, use the code below to get your `access_token`.\n",
|
||||||
|
"\n",
|
||||||
|
"*Important*: Each code is a one time code that will expire after use. The `access_token` can be used for a period of time. Make sure to copy paste the `access_token` once you get it!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Error: {'err': 'Code already used', 'ECODE': 'OAUTH_014'}\n",
|
||||||
|
"You already used this code once. Go back a step and generate a new code.\n",
|
||||||
|
"Our best guess for the url to get a new code is:\n",
|
||||||
|
"https://app.clickup.com/api?client_id=B5D61F8EVO04PR0JX0U73984LLS9GI6P&redirect_uri=https://google.com\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"access_token = ClickupAPIWrapper.get_access_token(oauth_client_id, oauth_client_secret, code)\n",
|
||||||
|
"\n",
|
||||||
|
"if access_token is not None:\n",
|
||||||
|
" print('Copy/paste this code, into the next cell so you can reuse it!')\n",
|
||||||
|
" print(access_token)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Create Toolkit"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Found team_id: 9011010153.\n",
|
||||||
|
"Most request require the team id, so we store it for you in the toolkit, we assume the first team in your list is the one you want. \n",
|
||||||
|
"Note: If you know this is the wrong ID, you can pass it at initialization.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Set your access token here\n",
|
||||||
|
"access_token = '12345678_myaccesstokengoeshere123'\n",
|
||||||
|
"access_token = '81928627_c009bf122ccf36ec3ba3e0ef748b07042c5e4217260042004a5934540cb61527'\n",
|
||||||
|
"\n",
|
||||||
|
"# Init toolkit\n",
|
||||||
|
"clickup_api_wrapper = ClickupAPIWrapper(access_token=access_token)\n",
|
||||||
|
"toolkit = ClickupToolkit.from_clickup_api_wrapper(clickup_api_wrapper)\n",
|
||||||
|
"print(f'Found team_id: {clickup_api_wrapper.team_id}.\\nMost request require the team id, so we store it for you in the toolkit, we assume the first team in your list is the one you want. \\nNote: If you know this is the wrong ID, you can pass it at initialization.')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Create Agent"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"llm = OpenAI(temperature=0, openai_api_key=\"\")\n",
|
||||||
|
"\n",
|
||||||
|
"agent = initialize_agent(\n",
|
||||||
|
" toolkit.get_tools(), llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n",
|
||||||
|
")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Run"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# helper function for demo\n",
|
||||||
|
"def print_and_run(command):\n",
|
||||||
|
" print('\\033[94m$ COMMAND\\033[0m')\n",
|
||||||
|
" print(command)\n",
|
||||||
|
" print('\\n\\033[94m$ AGENT\\033[0m')\n",
|
||||||
|
" response = agent.run(command)\n",
|
||||||
|
" print(''.join(['-']*80))\n",
|
||||||
|
" return response\n",
|
||||||
|
" "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Navigation\n",
|
||||||
|
"You can get the teams, folder and spaces your user has access to"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Get all the teams that the user is authorized to access\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to use the Get Teams tool\n",
|
||||||
|
"Action: Get Teams\n",
|
||||||
|
"Action Input: No necessary request parameters\u001b[0m\n",
|
||||||
|
"Observation: \u001b[38;5;200m\u001b[1;3m{'teams': [{'id': '9011010153', 'name': 'Task Copilot Sandbox Workspace 1', 'members': [{'id': 61681706, 'username': 'Aiswarya ', 'email': 'asankar@clickup.com', 'initials': 'A'}, {'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'email': 'rlentini@clickup.com', 'initials': 'RL'}]}]}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the teams the user is authorized to access\n",
|
||||||
|
"Final Answer: The user is authorized to access the team 'Task Copilot Sandbox Workspace 1'.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Get all the spaces available to the team\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to use the API to get the spaces\n",
|
||||||
|
"Action: Get Teams\n",
|
||||||
|
"Action Input: No necessary request parameters\u001b[0m\n",
|
||||||
|
"Observation: \u001b[38;5;200m\u001b[1;3m{'teams': [{'id': '9011010153', 'name': 'Task Copilot Sandbox Workspace 1', 'members': [{'id': 61681706, 'username': 'Aiswarya ', 'email': 'asankar@clickup.com', 'initials': 'A'}, {'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'email': 'rlentini@clickup.com', 'initials': 'RL'}]}]}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now have the list of teams\n",
|
||||||
|
"Final Answer: The list of teams available to the team is [{'id': '9011010153', 'name': 'Task Copilot Sandbox Workspace 1', 'members': [{'id': 61681706, 'username': 'Aiswarya ', 'email': 'asankar@clickup.com', 'initials': 'A'}, {'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'email': 'rlentini@clickup.com', 'initials': 'RL'}]}]\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Get all the folders for the team\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to get all the folders for the team\n",
|
||||||
|
"Action: Get all folders in the workspace\n",
|
||||||
|
"Action Input: {\"folder_id\": \"90130119692\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m{'spaces': [{'id': '90110075934', 'name': 'Test Space', 'color': None, 'private': False, 'avatar': None, 'admin_can_manage': False, 'statuses': [{'id': 'p90110075934_lBKIEh3r', 'status': 'Open', 'type': 'open', 'orderindex': 0, 'color': '#d3d3d3'}, {'id': 'p90110075934_AvVAnVqy', 'status': 'in progress', 'type': 'custom', 'orderindex': 1, 'color': '#a875ff'}, {'id': 'p90110075934_SftYWzGt', 'status': 'Closed', 'type': 'closed', 'orderindex': 2, 'color': '#6bc950'}], 'multiple_assignees': True, 'features': {'due_dates': {'enabled': True, 'start_date': True, 'remap_due_dates': False, 'remap_closed_due_date': False}, 'sprints': {'enabled': False}, 'time_tracking': {'enabled': True, 'harvest': False, 'rollup': False}, 'points': {'enabled': False}, 'custom_items': {'enabled': False}, 'priorities': {'enabled': True, 'priorities': [{'color': '#f50000', 'id': '1', 'orderindex': '1', 'priority': 'urgent'}, {'color': '#ffcc00', 'id': '2', 'orderindex': '2', 'priority': 'high'}, {'color': '#6fddff', 'id': '3', 'orderindex': '3', 'priority': 'normal'}, {'color': '#d8d8d8', 'id': '4', 'orderindex': '4', 'priority': 'low'}]}, 'tags': {'enabled': True}, 'check_unresolved': {'enabled': True, 'subtasks': None, 'checklists': None, 'comments': None}, 'zoom': {'enabled': False}, 'milestones': {'enabled': False}, 'custom_fields': {'enabled': True}, 'dependency_warning': {'enabled': True}, 'status_pies': {'enabled': False}, 'multiple_assignees': {'enabled': True}}, 'archived': False}]}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the folders in the team\n",
|
||||||
|
"Final Answer: The folders in the team are listed in the observation.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'The folders in the team are listed in the observation.'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 7,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"print_and_run(\"Get all the teams that the user is authorized to access\")\n",
|
||||||
|
"print_and_run(\"Get all the spaces available to the team\")\n",
|
||||||
|
"print_and_run(\"Get all the folders for the team\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Task Operations\n",
|
||||||
|
"You can get, ask question about tasks and update them"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 32,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"task_id = '8685mb5fn'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"#### Basic attirbute getting and updating"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 33,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Get task with id 8685mb5fn\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to use the Get task tool\n",
|
||||||
|
"Action: Get task\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[36;1m\u001b[1;3m{'id': '8685mb5fn', 'name': 'dummy task 1', 'text_content': 'An old, boring task description', 'description': 'An old, boring task description', 'status': 'to do', 'creator_id': 81928627, 'creator_username': 'Rodrigo Ceballos Lentini', 'creator_email': 'rlentini@clickup.com', 'assignees': [], 'watcher_username': 'Rodrigo Ceballos Lentini', 'watcher_email': 'rlentini@clickup.com', 'priority': 'high', 'due_date': '1694764800000', 'start_date': None, 'points': None, 'team_id': '9011010153', 'project_id': '90110331875'}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the task details\n",
|
||||||
|
"Final Answer: The task with id 8685mb5fn has the following details: {'id': '8685mb5fn', 'name': 'dummy task 1', 'text_content': 'An old, boring task description', 'description': 'An old, boring task description', 'status': 'to do', 'creator_id': 81928627, 'creator_username': 'Rodrigo Ceballos Lentini', 'creator_email': 'rlentini@clickup.com', 'assignees': [], 'watcher_username': 'Rodrigo Ceballos Lentini', 'watcher_email': 'rlentini@clickup.com', 'priority': 'high', 'due_date': '1694764800000', 'start_date': None, 'points': None, 'team_id': '9011010153', 'project_id': '90110331875'}\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"What is the description of task with id 8685mb5fn\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to get the description of the task\n",
|
||||||
|
"Action: Get task attribute\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"description\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3mAn old, boring task description\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the description of the task\n",
|
||||||
|
"Final Answer: An old, boring task description\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"For task with id 8685mb5fn, change the description to 'A cool task descriptiont changed by AI!'\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to update the description of a task\n",
|
||||||
|
"Action: Update task\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"description\", \"value\": \"A cool task description changed by AI!\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[36;1m\u001b[1;3m<Response [200]>\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I have successfully updated the task description\n",
|
||||||
|
"Final Answer: The description of task 8685mb5fn has been successfully changed to 'A cool task description changed by AI!'\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"What is the description of task with id 8685mb5fn\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to get the description of the task\n",
|
||||||
|
"Action: Get task attribute\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"description\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3mA cool task description changed by AI!\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the description of the task\n",
|
||||||
|
"Final Answer: A cool task description changed by AI!\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"For task with id 8685mb5fn, change the description to 'An old, boring task description'\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to update the description of a task\n",
|
||||||
|
"Action: Update task\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"description\", \"value\": \"An old, boring task description\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[36;1m\u001b[1;3m<Response [200]>\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the task description has been updated\n",
|
||||||
|
"Final Answer: The description of task 8685mb5fn has been updated to 'An old, boring task description'.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"\"The description of task 8685mb5fn has been updated to 'An old, boring task description'.\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 33,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# We can get a task to inspect it's contents\n",
|
||||||
|
"print_and_run(f\"Get task with id {task_id}\")\n",
|
||||||
|
"\n",
|
||||||
|
"# We can get a specific attribute from a task\n",
|
||||||
|
"previous_description = print_and_run(f\"What is the description of task with id {task_id}\")\n",
|
||||||
|
"\n",
|
||||||
|
"# We can even update it!\n",
|
||||||
|
"print_and_run(f\"For task with id {task_id}, change the description to 'A cool task descriptiont changed by AI!'\")\n",
|
||||||
|
"print_and_run(f\"What is the description of task with id {task_id}\")\n",
|
||||||
|
"\n",
|
||||||
|
"# Undo what we did\n",
|
||||||
|
"print_and_run(f\"For task with id {task_id}, change the description to '{previous_description}'\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 35,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Change the descrition task 8685mj6cd to 'Look ma no hands'\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to update the description of a task\n",
|
||||||
|
"Action: Update task\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mj6cd\", \"attribute_name\": \"description\", \"value\": \"Look ma no hands\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[36;1m\u001b[1;3m<Response [200]>\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m The task description has been successfully updated\n",
|
||||||
|
"Final Answer: The description of task 8685mj6cd has been changed to 'Look ma no hands'.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"\"The description of task 8685mj6cd has been changed to 'Look ma no hands'.\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 35,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"print_and_run(\"Change the descrition task 8685mj6cd to 'Look ma no hands'\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"#### Advanced Attributes (Assignees)\n",
|
||||||
|
"You can query and update almost every thing about a task!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 36,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"user_id = 81928627"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 37,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"What are the assignees of task id 8685mb5fn?\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to get the assignees of a task\n",
|
||||||
|
"Action: Get task attribute\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"assignee\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3mError: attribute_name = assignee was not found in task keys dict_keys(['id', 'name', 'text_content', 'description', 'status', 'creator_id', 'creator_username', 'creator_email', 'assignees', 'watcher_username', 'watcher_email', 'priority', 'due_date', 'start_date', 'points', 'team_id', 'project_id']). Please call again with one of the key names.\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I need to get the assignees of a task\n",
|
||||||
|
"Action: Get task attribute\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"assignees\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m[]\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||||
|
"Final Answer: There are no assignees for task id 8685mb5fn.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Remove user 81928627 from the assignees of task id 8685mb5fn\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to update the assignees of a task\n",
|
||||||
|
"Action: Update task assignees\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"operation\": \"rem\", \"users\": [81928627]}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m<Response [200]>\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m The user has been removed from the assignees of the task\n",
|
||||||
|
"Final Answer: User 81928627 has been removed from the assignees of task id 8685mb5fn.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"What are the assignees of task id 8685mb5fn?\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to get the assignees of a task\n",
|
||||||
|
"Action: Get task attribute\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"assignee\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3mError: attribute_name = assignee was not found in task keys dict_keys(['id', 'name', 'text_content', 'description', 'status', 'creator_id', 'creator_username', 'creator_email', 'assignees', 'watcher_username', 'watcher_email', 'priority', 'due_date', 'start_date', 'points', 'team_id', 'project_id']). Please call again with one of the key names.\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I need to get the assignees of a task\n",
|
||||||
|
"Action: Get task attribute\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"attribute_name\": \"assignees\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m[]\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||||
|
"Final Answer: There are no assignees for task id 8685mb5fn.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n",
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Add user 81928627 from the assignees of task id 8685mb5fn\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to update the assignees of a task\n",
|
||||||
|
"Action: Update task assignees\n",
|
||||||
|
"Action Input: {\"task_id\": \"8685mb5fn\", \"operation\": \"rem\", \"users\": [81928627]}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m<Response [200]>\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m The user has been removed from the assignees of the task\n",
|
||||||
|
"Final Answer: User 81928627 has been removed from the assignees of task id 8685mb5fn.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'User 81928627 has been removed from the assignees of task id 8685mb5fn.'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 37,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"print_and_run(f\"What are the assignees of task id {task_id}?\")\n",
|
||||||
|
"print_and_run(f\"Remove user {user_id} from the assignees of task id {task_id}\")\n",
|
||||||
|
"print_and_run(f\"What are the assignees of task id {task_id}?\")\n",
|
||||||
|
"print_and_run(f\"Add user {user_id} from the assignees of task id {task_id}\")\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Creation\n",
|
||||||
|
"You can create tasks, lists and folders"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 27,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Create a task called 'Test Task - 18/09/2023-10:31:22' with description 'This is a Test'\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to use the Create Task tool\n",
|
||||||
|
"Action: Create Task\n",
|
||||||
|
"Action Input: {\"name\": \"Test Task - 18/09/2023-10:31:22\", \"description\": \"This is a Test\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[36;1m\u001b[1;3m{'id': '8685mw4wq', 'custom_id': None, 'name': 'Test Task - 18/09/2023-10:31:22', 'text_content': 'This is a Test', 'description': 'This is a Test', 'status': {'id': 'p90110061901_VlN8IJtk', 'status': 'to do', 'color': '#87909e', 'orderindex': 0, 'type': 'open'}, 'orderindex': '23.00000000000000000000000000000000', 'date_created': '1695047486396', 'date_updated': '1695047486396', 'date_closed': None, 'date_done': None, 'archived': False, 'creator': {'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'color': '#c51162', 'email': 'rlentini@clickup.com', 'profilePicture': None}, 'assignees': [], 'watchers': [{'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'color': '#c51162', 'initials': 'RL', 'email': 'rlentini@clickup.com', 'profilePicture': None}], 'checklists': [], 'tags': [], 'parent': None, 'priority': None, 'due_date': None, 'start_date': None, 'points': None, 'time_estimate': None, 'time_spent': 0, 'custom_fields': [], 'dependencies': [], 'linked_tasks': [], 'team_id': '9011010153', 'url': 'https://app.clickup.com/t/8685mw4wq', 'sharing': {'public': False, 'public_share_expires_on': None, 'public_fields': ['assignees', 'priority', 'due_date', 'content', 'comments', 'attachments', 'customFields', 'subtasks', 'tags', 'checklists', 'coverimage'], 'token': None, 'seo_optimized': False}, 'permission_level': 'create', 'list': {'id': '901100754275', 'name': 'Test List', 'access': True}, 'project': {'id': '90110336890', 'name': 'Test Folder', 'hidden': False, 'access': True}, 'folder': {'id': '90110336890', 'name': 'Test Folder', 'hidden': False, 'access': True}, 'space': {'id': '90110061901'}}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||||
|
"Final Answer: A task called 'Test Task - 18/09/2023-10:31:22' with description 'This is a Test' was successfully created.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"\"A task called 'Test Task - 18/09/2023-10:31:22' with description 'This is a Test' was successfully created.\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 27,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"time_str = datetime.now().strftime(\"%d/%m/%Y-%H:%M:%S\")\n",
|
||||||
|
"print_and_run(f\"Create a task called 'Test Task - {time_str}' with description 'This is a Test'\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 29,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Create a list called Test List - 18/09/2023-10:32:12\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to create a list\n",
|
||||||
|
"Action: Create List\n",
|
||||||
|
"Action Input: {\"name\": \"Test List - 18/09/2023-10:32:12\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m{'id': '901100774700', 'name': 'Test List - 18/09/2023-10:32:12', 'deleted': False, 'orderindex': 13, 'content': '', 'priority': None, 'assignee': None, 'due_date': None, 'start_date': None, 'folder': {'id': '90110336890', 'name': 'Test Folder', 'hidden': False, 'access': True}, 'space': {'id': '90110061901', 'name': 'Space', 'access': True}, 'inbound_address': 'a.t.901100774700.u-81928627.20b87d50-eece-4721-b487-9ca500338587@tasks.clickup.com', 'archived': False, 'override_statuses': False, 'statuses': [{'id': 'p90110061901_VlN8IJtk', 'status': 'to do', 'orderindex': 0, 'color': '#87909e', 'type': 'open'}, {'id': 'p90110061901_14GpYKnM', 'status': 'complete', 'orderindex': 1, 'color': '#6bc950', 'type': 'closed'}], 'permission_level': 'create'}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||||
|
"Final Answer: The list \"Test List - 18/09/2023-10:32:12\" has been created with id 901100774700.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"'The list \"Test List - 18/09/2023-10:32:12\" has been created with id 901100774700.'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 29,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"time_str = datetime.now().strftime(\"%d/%m/%Y-%H:%M:%S\")\n",
|
||||||
|
"print_and_run(f\"Create a list called Test List - {time_str}\")\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 30,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Create a folder called 'Test Folder - 18/09/2023-10:32:51'\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to use the Create Folder tool\n",
|
||||||
|
"Action: Create Folder\n",
|
||||||
|
"Action Input: {\"name\": \"Test Folder - 18/09/2023-10:32:51\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[38;5;200m\u001b[1;3m{'id': '90110348711', 'name': 'Test Folder - 18/09/2023-10:32:51', 'orderindex': 12, 'override_statuses': False, 'hidden': False, 'space': {'id': '90110061901', 'name': 'Space', 'access': True}, 'task_count': '0', 'archived': False, 'statuses': [], 'lists': [], 'permission_level': 'create'}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I have successfully created the folder\n",
|
||||||
|
"Final Answer: The folder 'Test Folder - 18/09/2023-10:32:51' has been successfully created.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"\"The folder 'Test Folder - 18/09/2023-10:32:51' has been successfully created.\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 30,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"time_str = datetime.now().strftime(\"%d/%m/%Y-%H:%M:%S\")\n",
|
||||||
|
"print_and_run(f\"Create a folder called 'Test Folder - {time_str}'\")\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 32,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Create a list called 'Test List - 18/09/2023-10:34:01' with content My test list with high priority and status red\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to create a list with the given name, content, priority and status\n",
|
||||||
|
"Action: Create List\n",
|
||||||
|
"Action Input: {\"name\": \"Test List - 18/09/2023-10:34:01\", \"content\": \"My test list\", \"priority\": 2, \"status\": \"red\"}\u001b[0m\n",
|
||||||
|
"Observation: \u001b[33;1m\u001b[1;3m{'id': '901100774746', 'name': 'Test List - 18/09/2023-10:34:01', 'deleted': False, 'orderindex': 15, 'content': '', 'status': {'status': 'red', 'color': '#e50000', 'hide_label': True}, 'priority': {'priority': 'high', 'color': '#ffcc00'}, 'assignee': None, 'due_date': None, 'start_date': None, 'folder': {'id': '90110336890', 'name': 'Test Folder', 'hidden': False, 'access': True}, 'space': {'id': '90110061901', 'name': 'Space', 'access': True}, 'inbound_address': 'a.t.901100774746.u-81928627.2ab87133-728e-4166-b2ae-423cc320df37@tasks.clickup.com', 'archived': False, 'override_statuses': False, 'statuses': [{'id': 'p90110061901_VlN8IJtk', 'status': 'to do', 'orderindex': 0, 'color': '#87909e', 'type': 'open'}, {'id': 'p90110061901_14GpYKnM', 'status': 'complete', 'orderindex': 1, 'color': '#6bc950', 'type': 'closed'}], 'permission_level': 'create'}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I have successfully created the list\n",
|
||||||
|
"Final Answer: The list 'Test List - 18/09/2023-10:34:01' with content 'My test list' with high priority and status red has been successfully created.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"\"The list 'Test List - 18/09/2023-10:34:01' with content 'My test list' with high priority and status red has been successfully created.\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 32,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"time_str = datetime.now().strftime(\"%d/%m/%Y-%H:%M:%S\")\n",
|
||||||
|
"print_and_run(f\"Create a list called 'Test List - {time_str}' with content My test list with high priority and status red\")\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Multi-Step Tasks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 34,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[94m$ COMMAND\u001b[0m\n",
|
||||||
|
"Figure out what user ID Rodrigo is, create a task called 'Rod's task', assign it to Rodrigo\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[94m$ AGENT\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
|
||||||
|
"\u001b[32;1m\u001b[1;3m I need to get the user ID of Rodrigo, create a task, and assign it to Rodrigo\n",
|
||||||
|
"Action: Get Teams\n",
|
||||||
|
"Action Input: No input necessary\u001b[0m\n",
|
||||||
|
"Observation: \u001b[38;5;200m\u001b[1;3m{'teams': [{'id': '9011010153', 'name': 'Task Copilot Sandbox Workspace 1', 'members': [{'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'email': 'rlentini@clickup.com', 'initials': 'RL'}]}]}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now have the user ID of Rodrigo\n",
|
||||||
|
"Action: Create Task\n",
|
||||||
|
"Action Input: {\"name\": \"Rod's task\", \"assignees\": [81928627]}\u001b[0m"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"/Users/rodrigolentini/repos/langchain-clickup/libs/langchain/langchain/utilities/clickup.py:145: UserWarning: Error encountered while trying to parse <class 'langchain.utilities.clickup.Task'>: 'NoneType' object is not subscriptable\n",
|
||||||
|
" Falling back to returning input data.\n",
|
||||||
|
" warnings.warn(f'Error encountered while trying to parse {dataclass}: {e}\\n Falling back to returning input data.')\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\n",
|
||||||
|
"Observation: \u001b[36;1m\u001b[1;3m{'id': '8685mw6dz', 'custom_id': None, 'name': \"Rod's task\", 'text_content': '', 'description': '', 'status': {'id': 'p90110061901_VlN8IJtk', 'status': 'to do', 'color': '#87909e', 'orderindex': 0, 'type': 'open'}, 'orderindex': '24.00000000000000000000000000000000', 'date_created': '1695047740939', 'date_updated': '1695047740939', 'date_closed': None, 'date_done': None, 'archived': False, 'creator': {'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'color': '#c51162', 'email': 'rlentini@clickup.com', 'profilePicture': None}, 'assignees': [{'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'color': '#c51162', 'initials': 'RL', 'email': 'rlentini@clickup.com', 'profilePicture': None}], 'watchers': [{'id': 81928627, 'username': 'Rodrigo Ceballos Lentini', 'color': '#c51162', 'initials': 'RL', 'email': 'rlentini@clickup.com', 'profilePicture': None}], 'checklists': [], 'tags': [], 'parent': None, 'priority': None, 'due_date': None, 'start_date': None, 'points': None, 'time_estimate': None, 'time_spent': 0, 'custom_fields': [], 'dependencies': [], 'linked_tasks': [], 'team_id': '9011010153', 'url': 'https://app.clickup.com/t/8685mw6dz', 'sharing': {'public': False, 'public_share_expires_on': None, 'public_fields': ['assignees', 'priority', 'due_date', 'content', 'comments', 'attachments', 'customFields', 'subtasks', 'tags', 'checklists', 'coverimage'], 'token': None, 'seo_optimized': False}, 'permission_level': 'create', 'list': {'id': '901100754275', 'name': 'Test List', 'access': True}, 'project': {'id': '90110336890', 'name': 'Test Folder', 'hidden': False, 'access': True}, 'folder': {'id': '90110336890', 'name': 'Test Folder', 'hidden': False, 'access': True}, 'space': {'id': '90110061901'}}\u001b[0m\n",
|
||||||
|
"Thought:\u001b[32;1m\u001b[1;3m I now have the task created and assigned to Rodrigo\n",
|
||||||
|
"Final Answer: Rodrigo's user ID is 81928627 and a task called 'Rod's task' has been created and assigned to him.\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||||
|
"--------------------------------------------------------------------------------\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"\"Rodrigo's user ID is 81928627 and a task called 'Rod's task' has been created and assigned to him.\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 34,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"print_and_run(\"Figure out what user ID Rodrigo is, create a task called 'Rod's task', assign it to Rodrigo\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "clickup-copilot",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.9.18"
|
||||||
|
},
|
||||||
|
"orig_nbformat": 4
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
|
from langchain.tools import BaseTool
|
||||||
|
from langchain.tools.clickup.prompt import (
|
||||||
|
CLICKUP_FOLDER_CREATE_PROMPT,
|
||||||
|
CLICKUP_GET_ALL_TEAMS_PROMPT,
|
||||||
|
CLICKUP_GET_FOLDERS_PROMPT,
|
||||||
|
CLICKUP_GET_LIST_PROMPT,
|
||||||
|
CLICKUP_GET_SPACES_PROMPT,
|
||||||
|
CLICKUP_GET_TASK_ATTRIBUTE_PROMPT,
|
||||||
|
CLICKUP_GET_TASK_PROMPT,
|
||||||
|
CLICKUP_LIST_CREATE_PROMPT,
|
||||||
|
CLICKUP_TASK_CREATE_PROMPT,
|
||||||
|
CLICKUP_UPDATE_TASK_ASSIGNEE_PROMPT,
|
||||||
|
CLICKUP_UPDATE_TASK_PROMPT,
|
||||||
|
)
|
||||||
|
from langchain.tools.clickup.tool import ClickupAction
|
||||||
|
from langchain.utilities.clickup import ClickupAPIWrapper
|
||||||
|
|
||||||
|
|
||||||
|
class ClickupToolkit(BaseToolkit):
|
||||||
|
"""Clickup Toolkit."""
|
||||||
|
|
||||||
|
tools: List[BaseTool] = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_clickup_api_wrapper(
|
||||||
|
cls, clickup_api_wrapper: ClickupAPIWrapper
|
||||||
|
) -> "ClickupToolkit":
|
||||||
|
operations: List[Dict] = [
|
||||||
|
{
|
||||||
|
"mode": "get_task",
|
||||||
|
"name": "Get task",
|
||||||
|
"description": CLICKUP_GET_TASK_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "get_task_attribute",
|
||||||
|
"name": "Get task attribute",
|
||||||
|
"description": CLICKUP_GET_TASK_ATTRIBUTE_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "get_teams",
|
||||||
|
"name": "Get Teams",
|
||||||
|
"description": CLICKUP_GET_ALL_TEAMS_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "create_task",
|
||||||
|
"name": "Create Task",
|
||||||
|
"description": CLICKUP_TASK_CREATE_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "create_list",
|
||||||
|
"name": "Create List",
|
||||||
|
"description": CLICKUP_LIST_CREATE_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "create_folder",
|
||||||
|
"name": "Create Folder",
|
||||||
|
"description": CLICKUP_FOLDER_CREATE_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "get_list",
|
||||||
|
"name": "Get all lists in the space",
|
||||||
|
"description": CLICKUP_GET_LIST_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "get_folders",
|
||||||
|
"name": "Get all folders in the workspace",
|
||||||
|
"description": CLICKUP_GET_FOLDERS_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "get_spaces",
|
||||||
|
"name": "Get all spaces in the workspace",
|
||||||
|
"description": CLICKUP_GET_SPACES_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "update_task",
|
||||||
|
"name": "Update task",
|
||||||
|
"description": CLICKUP_UPDATE_TASK_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "update_task_assignees",
|
||||||
|
"name": "Update task assignees",
|
||||||
|
"description": CLICKUP_UPDATE_TASK_ASSIGNEE_PROMPT,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
tools = [
|
||||||
|
ClickupAction(
|
||||||
|
name=action["name"],
|
||||||
|
description=action["description"],
|
||||||
|
mode=action["mode"],
|
||||||
|
api_wrapper=clickup_api_wrapper,
|
||||||
|
)
|
||||||
|
for action in operations
|
||||||
|
]
|
||||||
|
return cls(tools=tools)
|
||||||
|
|
||||||
|
def get_tools(self) -> List[BaseTool]:
|
||||||
|
"""Get the tools in the toolkit."""
|
||||||
|
return self.tools
|
0
libs/langchain/langchain/tools/clickup/__init__.py
Normal file
0
libs/langchain/langchain/tools/clickup/__init__.py
Normal file
131
libs/langchain/langchain/tools/clickup/prompt.py
Normal file
131
libs/langchain/langchain/tools/clickup/prompt.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# flake8: noqa
|
||||||
|
CLICKUP_TASK_CREATE_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's create_task API, useful when you need to create a CLICKUP task.
|
||||||
|
The input to this tool is a dictionary specifying the fields of the CLICKUP task, and will be passed into clickup's CLICKUP `create_task` function.
|
||||||
|
Only add fields described by the user.
|
||||||
|
Use the following mapping in order to map the user's priority to the clickup priority: {{
|
||||||
|
Urgent = 1,
|
||||||
|
High = 2,
|
||||||
|
Normal = 3,
|
||||||
|
Low = 4,
|
||||||
|
}}. If the user passes in "urgent" replace the priority value as 1.
|
||||||
|
|
||||||
|
Here are a few task descriptions and corresponding input examples:
|
||||||
|
Task: create a task called "Daily report"
|
||||||
|
Example Input: {{"name": "Daily report"}}
|
||||||
|
Task: Make an open task called "ClickUp toolkit refactor" with description "Refactor the clickup toolkit to use dataclasses for parsing", with status "open"
|
||||||
|
Example Input: {{"name": "ClickUp toolkit refactor", "description": "Refactor the clickup toolkit to use dataclasses for parsing", "status": "Open"}}
|
||||||
|
Task: create a task with priority 3 called "New Task Name" with description "New Task Description", with status "open"
|
||||||
|
Example Input: {{"name": "New Task Name", "description": "New Task Description", "status": "Open", "priority": 3}}
|
||||||
|
Task: Add a task called "Bob's task" and assign it to Bob (user id: 81928627)
|
||||||
|
Example Input: {{"name": "Bob's task", "description": "Task for Bob", "assignees": [81928627]}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_LIST_CREATE_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's create_list API, useful when you need to create a CLICKUP list.
|
||||||
|
The input to this tool is a dictionary specifying the fields of a clickup list, and will be passed to clickup's create_list function.
|
||||||
|
Only add fields described by the user.
|
||||||
|
Use the following mapping in order to map the user's priority to the clickup priority: {{
|
||||||
|
Urgent = 1,
|
||||||
|
High = 2,
|
||||||
|
Normal = 3,
|
||||||
|
Low = 4,
|
||||||
|
}}. If the user passes in "urgent" replace the priority value as 1.
|
||||||
|
|
||||||
|
Here are a few list descriptions and corresponding input examples:
|
||||||
|
Description: make a list with name "General List"
|
||||||
|
Example Input: {{"name": "General List"}}
|
||||||
|
Description: add a new list ("TODOs") with low priority
|
||||||
|
Example Input: {{"name": "General List", "priority": 4}}
|
||||||
|
Description: create a list with name "List name", content "List content", priority 2, and status "red"
|
||||||
|
Example Input: {{"name": "List name", "content": "List content", "priority": 2, "status": "red"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_FOLDER_CREATE_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's create_folder API, useful when you need to create a CLICKUP folder.
|
||||||
|
The input to this tool is a dictionary specifying the fields of a clickup folder, and will be passed to clickup's create_folder function.
|
||||||
|
For example, to create a folder with name "Folder name" you would pass in the following dictionary:
|
||||||
|
{{
|
||||||
|
"name": "Folder name",
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_TASK_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
Do NOT use to get a task specific attribute. Use get task attribute instead.
|
||||||
|
useful when you need to get a specific task for the user. Given the task id you want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"task_id": "86a0t44tq"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_TASK_ATTRIBUTE_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to get a specific attribute from a task. Given the task id and desired attribute create a request similar to the following dictionary:
|
||||||
|
payload = {{"task_id": "<task_id_to_update>", "attribute_name": "<attribute_name_to_update>"}}
|
||||||
|
|
||||||
|
Here are some example queries their corresponding payloads:
|
||||||
|
Get the name of task 23jn23kjn -> {{"task_id": "23jn23kjn", "attribute_name": "name"}}
|
||||||
|
What is the priority of task 86a0t44tq? -> {{"task_id": "86a0t44tq", "attribute_name": "priority"}}
|
||||||
|
Output the description of task sdc9ds9jc -> {{"task_id": "sdc9ds9jc", "attribute_name": "description"}}
|
||||||
|
Who is assigned to task bgjfnbfg0 -> {{"task_id": "bgjfnbfg0", "attribute_name": "assignee"}}
|
||||||
|
Which is the status of task kjnsdcjc? -> {{"task_id": "kjnsdcjc", "attribute_name": "description"}}
|
||||||
|
How long is the time estimate of task sjncsd999? -> {{"task_id": "sjncsd999", "attribute_name": "time_estimate"}}
|
||||||
|
Is task jnsd98sd archived?-> {{"task_id": "jnsd98sd", "attribute_name": "archive"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_ALL_TEAMS_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API, useful when you need to get all teams that the user is a part of.
|
||||||
|
To get a list of all the teams there is no necessary request parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_LIST_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to get a specific list for the user. Given the list id you want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"list_id": "901300608424"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_FOLDERS_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to get a specific folder for the user. Given the user's workspace id you want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"folder_id": "90130119692"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_SPACES_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to get all the spaces available to a user. Given the user's workspace id you want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"team_id": "90130119692"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_GET_SPACES_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to get all the spaces available to a user. Given the user's workspace id you want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"team_id": "90130119692"}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_UPDATE_TASK_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to update a specific attribute of a task. Given the task id, desired attribute to change and the new value you want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"task_id": "<task_id_to_update>", "attribute_name": "<attribute_name_to_update>", "value": "<value_to_update_to>"}}
|
||||||
|
|
||||||
|
Here are some example queries their corresponding payloads:
|
||||||
|
Change the name of task 23jn23kjn to new task name -> {{"task_id": "23jn23kjn", "attribute_name": "name", "value": "new task name"}}
|
||||||
|
Update the priority of task 86a0t44tq to 1 -> {{"task_id": "86a0t44tq", "attribute_name": "priority", "value": 1}}
|
||||||
|
Re-write the description of task sdc9ds9jc to 'a new task description' -> {{"task_id": "sdc9ds9jc", "attribute_name": "description", "value": "a new task description"}}
|
||||||
|
Forward the status of task kjnsdcjc to done -> {{"task_id": "kjnsdcjc", "attribute_name": "description", "status": "done"}}
|
||||||
|
Increase the time estimate of task sjncsd999 to 3h -> {{"task_id": "sjncsd999", "attribute_name": "time_estimate", "value": 8000}}
|
||||||
|
Archive task jnsd98sd -> {{"task_id": "jnsd98sd", "attribute_name": "archive", "value": true}}
|
||||||
|
*IMPORTANT*: Pay attention to the exact syntax above and the correct use of quotes.
|
||||||
|
For changing priority and time estimates, we expect integers (int).
|
||||||
|
For name, description and status we expect strings (str).
|
||||||
|
For archive, we expect a boolean (bool).
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLICKUP_UPDATE_TASK_ASSIGNEE_PROMPT = """
|
||||||
|
This tool is a wrapper around clickup's API,
|
||||||
|
useful when you need to update the assignees of a task. Given the task id, the operation add or remove (rem), and the list of user ids. You want to create a request similar to the following dictionary:
|
||||||
|
payload = {{"task_id": "<task_id_to_update>", "operation": "<operation, (add or rem)>", "users": [<user_id_1>, <user_id_2>]}}
|
||||||
|
|
||||||
|
Here are some example queries their corresponding payloads:
|
||||||
|
Add 81928627 and 3987234 as assignees to task 21hw21jn -> {{"task_id": "21hw21jn", "operation": "add", "users": [81928627, 3987234]}}
|
||||||
|
Remove 67823487 as assignee from task jin34ji4 -> {{"task_id": "jin34ji4", "operation": "rem", "users": [67823487]}}
|
||||||
|
*IMPORTANT*: Users id should always be ints.
|
||||||
|
"""
|
51
libs/langchain/langchain/tools/clickup/tool.py
Normal file
51
libs/langchain/langchain/tools/clickup/tool.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
This tool allows agents to interact with the clickup library
|
||||||
|
and operate on a Clickup instance.
|
||||||
|
To use this tool, you must first set as environment variables:
|
||||||
|
client_secret
|
||||||
|
client_id
|
||||||
|
code
|
||||||
|
|
||||||
|
Below is a sample script that uses the Clickup tool:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from langchain.agents import AgentType
|
||||||
|
from langchain.agents import initialize_agent
|
||||||
|
from langchain.agents.agent_toolkits.clickup.toolkit import ClickupToolkit
|
||||||
|
from langchain.llms import OpenAI
|
||||||
|
from langchain.utilities.clickup import ClickupAPIWrapper
|
||||||
|
|
||||||
|
llm = OpenAI(temperature=0)
|
||||||
|
clickup = ClickupAPIWrapper()
|
||||||
|
toolkit = ClickupToolkit.from_clickup_api_wrapper(clickup)
|
||||||
|
agent = initialize_agent(
|
||||||
|
toolkit.get_tools(),
|
||||||
|
llm,
|
||||||
|
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||||
|
verbose=True
|
||||||
|
)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.callbacks.manager import CallbackManagerForToolRun
|
||||||
|
from langchain.pydantic_v1 import Field
|
||||||
|
from langchain.tools.base import BaseTool
|
||||||
|
from langchain.utilities.clickup import ClickupAPIWrapper
|
||||||
|
|
||||||
|
|
||||||
|
class ClickupAction(BaseTool):
|
||||||
|
"""Tool that queries the Clickup API."""
|
||||||
|
|
||||||
|
api_wrapper: ClickupAPIWrapper = Field(default_factory=ClickupAPIWrapper)
|
||||||
|
mode: str
|
||||||
|
name: str = ""
|
||||||
|
description: str = ""
|
||||||
|
|
||||||
|
def _run(
|
||||||
|
self,
|
||||||
|
instructions: str,
|
||||||
|
run_manager: Optional[CallbackManagerForToolRun] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Use the Clickup API to run an operation."""
|
||||||
|
return self.api_wrapper.run(self.mode, instructions)
|
597
libs/langchain/langchain/utilities/clickup.py
Normal file
597
libs/langchain/langchain/utilities/clickup.py
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
"""Util that calls clickup."""
|
||||||
|
import json
|
||||||
|
import warnings
|
||||||
|
from dataclasses import asdict, dataclass, fields
|
||||||
|
from typing import Any, Dict, List, Mapping, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from langchain.pydantic_v1 import BaseModel, Extra, root_validator
|
||||||
|
from langchain.utils import get_from_dict_or_env
|
||||||
|
|
||||||
|
DEFAULT_URL = "https://api.clickup.com/api/v2"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Component:
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Dict[str, Any]) -> "Component":
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Task(Component):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
text_content: str
|
||||||
|
description: str
|
||||||
|
status: str
|
||||||
|
creator_id: int
|
||||||
|
creator_username: str
|
||||||
|
creator_email: str
|
||||||
|
assignees: List[Dict[str, Any]]
|
||||||
|
watchers: List[Dict[str, Any]]
|
||||||
|
priority: Optional[str]
|
||||||
|
due_date: Optional[str]
|
||||||
|
start_date: Optional[str]
|
||||||
|
points: int
|
||||||
|
team_id: int
|
||||||
|
project_id: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Dict[str, Any]) -> "Task":
|
||||||
|
priority = None if data["priority"] is None else data["priority"]["priority"]
|
||||||
|
return cls(
|
||||||
|
id=data["id"],
|
||||||
|
name=data["name"],
|
||||||
|
text_content=data["text_content"],
|
||||||
|
description=data["description"],
|
||||||
|
status=data["status"]["status"],
|
||||||
|
creator_id=data["creator"]["id"],
|
||||||
|
creator_username=data["creator"]["username"],
|
||||||
|
creator_email=data["creator"]["email"],
|
||||||
|
assignees=data["assignees"],
|
||||||
|
watchers=data["watchers"],
|
||||||
|
priority=priority,
|
||||||
|
due_date=data["due_date"],
|
||||||
|
start_date=data["start_date"],
|
||||||
|
points=data["points"],
|
||||||
|
team_id=data["team_id"],
|
||||||
|
project_id=data["project"]["id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CUList(Component):
|
||||||
|
folder_id: float
|
||||||
|
name: str
|
||||||
|
content: Optional[str] = None
|
||||||
|
due_date: Optional[int] = None
|
||||||
|
due_date_time: Optional[bool] = None
|
||||||
|
priority: Optional[int] = None
|
||||||
|
assignee: Optional[int] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: dict) -> "CUList":
|
||||||
|
return cls(
|
||||||
|
folder_id=data["folder_id"],
|
||||||
|
name=data["name"],
|
||||||
|
content=data.get("content"),
|
||||||
|
due_date=data.get("due_date"),
|
||||||
|
due_date_time=data.get("due_date_time"),
|
||||||
|
priority=data.get("priority"),
|
||||||
|
assignee=data.get("assignee"),
|
||||||
|
status=data.get("status"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Member(Component):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
initials: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Dict) -> "Member":
|
||||||
|
return cls(
|
||||||
|
id=data["user"]["id"],
|
||||||
|
username=data["user"]["username"],
|
||||||
|
email=data["user"]["email"],
|
||||||
|
initials=data["user"]["initials"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Team(Component):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
members: List[Member]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Dict) -> "Team":
|
||||||
|
members = [Member.from_data(member_data) for member_data in data["members"]]
|
||||||
|
return cls(id=data["id"], name=data["name"], members=members)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Space(Component):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
private: bool
|
||||||
|
enabled_features: Dict[str, Any]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: Dict[str, Any]) -> "Space":
|
||||||
|
space_data = data["spaces"][0]
|
||||||
|
enabled_features = {
|
||||||
|
feature: value
|
||||||
|
for feature, value in space_data["features"].items()
|
||||||
|
if value["enabled"]
|
||||||
|
}
|
||||||
|
return cls(
|
||||||
|
id=space_data["id"],
|
||||||
|
name=space_data["name"],
|
||||||
|
private=space_data["private"],
|
||||||
|
enabled_features=enabled_features,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dict_through_component(
|
||||||
|
data: dict, component: Type[Component], fault_tolerant: bool = False
|
||||||
|
) -> Dict:
|
||||||
|
"""This is a helper function that helps us parse a dictionary by creating
|
||||||
|
a component and then turning it back into a dictionary. This might seem
|
||||||
|
silly but it's a nice way to:
|
||||||
|
1. Extract and format data from a dictionary according to a schema
|
||||||
|
2. Provide a central place to do this in a fault tolerant way
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return asdict(component.from_data(data))
|
||||||
|
except Exception as e:
|
||||||
|
if fault_tolerant:
|
||||||
|
warning_str = f"""Error encountered while trying to parse
|
||||||
|
{str(data)}: {str(e)}\n Falling back to returning input data."""
|
||||||
|
warnings.warn(warning_str)
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def extract_dict_elements_from_component_fields(
|
||||||
|
data: dict, component: Type[Component]
|
||||||
|
) -> dict:
|
||||||
|
output = {}
|
||||||
|
for attribute in fields(component):
|
||||||
|
if attribute.name in data:
|
||||||
|
output[attribute.name] = data[attribute.name]
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def load_query(
|
||||||
|
query: str, fault_tolerant: bool = False
|
||||||
|
) -> Tuple[Optional[Dict], Optional[str]]:
|
||||||
|
"""
|
||||||
|
Attempts to parse a JSON string and return the parsed object.
|
||||||
|
If parsing fails, returns an error message.
|
||||||
|
|
||||||
|
:param query: The JSON string to parse.
|
||||||
|
:return: A tuple containing the parsed object or None and an error message or None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return json.loads(query), None
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
if fault_tolerant:
|
||||||
|
return (
|
||||||
|
None,
|
||||||
|
f"""Input must be a valid JSON. Got the following error: {str(e)}.
|
||||||
|
"Please reformat and try again.""",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_first_id(data: dict, key: str) -> Optional[int]:
|
||||||
|
if key in data and len(data[key]) > 0:
|
||||||
|
if len(data[key]) > 1:
|
||||||
|
warnings.warn(f"Found multiple {key}: {data[key]}. Defaulting to first.")
|
||||||
|
return data[key][0]["id"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_data(url: str, access_token: str, query: Optional[dict] = None) -> dict:
|
||||||
|
headers = {"Authorization": access_token}
|
||||||
|
response = requests.get(url, headers=headers, params=query)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_team_id(access_token: str) -> Optional[int]:
|
||||||
|
url = f"{DEFAULT_URL}/team"
|
||||||
|
data = fetch_data(url, access_token)
|
||||||
|
return fetch_first_id(data, "teams")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_space_id(team_id: int, access_token: str) -> Optional[int]:
|
||||||
|
url = f"{DEFAULT_URL}/team/{team_id}/space"
|
||||||
|
data = fetch_data(url, access_token, query={"archived": "false"})
|
||||||
|
return fetch_first_id(data, "spaces")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_folder_id(space_id: int, access_token: str) -> Optional[int]:
|
||||||
|
url = f"{DEFAULT_URL}/space/{space_id}/folder"
|
||||||
|
data = fetch_data(url, access_token, query={"archived": "false"})
|
||||||
|
return fetch_first_id(data, "folders")
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_list_id(space_id: int, folder_id: int, access_token: str) -> Optional[int]:
|
||||||
|
if folder_id:
|
||||||
|
url = f"{DEFAULT_URL}/folder/{folder_id}/list"
|
||||||
|
else:
|
||||||
|
url = f"{DEFAULT_URL}/space/{space_id}/list"
|
||||||
|
|
||||||
|
data = fetch_data(url, access_token, query={"archived": "false"})
|
||||||
|
|
||||||
|
# The structure to fetch list id differs based if its folderless
|
||||||
|
if folder_id and "id" in data:
|
||||||
|
return data["id"]
|
||||||
|
else:
|
||||||
|
return fetch_first_id(data, "lists")
|
||||||
|
|
||||||
|
|
||||||
|
class ClickupAPIWrapper(BaseModel):
|
||||||
|
"""Wrapper for Clickup API."""
|
||||||
|
|
||||||
|
access_token: Optional[str] = None
|
||||||
|
team_id: Optional[str] = None
|
||||||
|
space_id: Optional[str] = None
|
||||||
|
folder_id: Optional[str] = None
|
||||||
|
list_id: Optional[str] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""Configuration for this pydantic object."""
|
||||||
|
|
||||||
|
extra = Extra.forbid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_access_code_url(
|
||||||
|
cls, oauth_client_id: str, redirect_uri: str = "https://google.com"
|
||||||
|
) -> str:
|
||||||
|
url = f"https://app.clickup.com/api?client_id={oauth_client_id}"
|
||||||
|
return f"{url}&redirect_uri={redirect_uri}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_access_token(
|
||||||
|
cls, oauth_client_id: str, oauth_client_secret: str, code: str
|
||||||
|
) -> Optional[str]:
|
||||||
|
url = f"{DEFAULT_URL}/oauth/token"
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"client_id": oauth_client_id,
|
||||||
|
"client_secret": oauth_client_secret,
|
||||||
|
"code": code,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, params=params)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if "access_token" not in data:
|
||||||
|
print(f"Error: {data}")
|
||||||
|
if "ECODE" in data and data["ECODE"] == "OAUTH_014":
|
||||||
|
url = ClickupAPIWrapper.get_access_code_url(oauth_client_id)
|
||||||
|
print(
|
||||||
|
"You already used this code once. Generate a new one.",
|
||||||
|
f"Our best guess for the url to get a new code is:\n{url}",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data["access_token"]
|
||||||
|
|
||||||
|
@root_validator()
|
||||||
|
def validate_environment(cls, values: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
Validate that api key and python package exists in environment
|
||||||
|
"""
|
||||||
|
values["access_token"] = get_from_dict_or_env(
|
||||||
|
values, "access_token", "CLICKUP_ACCESS_TOKEN"
|
||||||
|
)
|
||||||
|
values["team_id"] = fetch_team_id(values["access_token"])
|
||||||
|
values["space_id"] = fetch_space_id(values["team_id"], values["access_token"])
|
||||||
|
values["folder_id"] = fetch_folder_id(
|
||||||
|
values["space_id"], values["access_token"]
|
||||||
|
)
|
||||||
|
values["list_id"] = fetch_list_id(
|
||||||
|
values["space_id"], values["folder_id"], values["access_token"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
def attempt_parse_teams(self, input_dict: dict) -> Dict[str, List[dict]]:
|
||||||
|
"""
|
||||||
|
Parse appropriate content from the list of teams
|
||||||
|
"""
|
||||||
|
|
||||||
|
parsed_teams: Dict[str, List[dict]] = {"teams": []}
|
||||||
|
for team in input_dict["teams"]:
|
||||||
|
try:
|
||||||
|
team = parse_dict_through_component(team, Team, fault_tolerant=False)
|
||||||
|
parsed_teams["teams"].append(team)
|
||||||
|
except Exception as e:
|
||||||
|
warnings.warn(f"Error parsing a team {e}")
|
||||||
|
|
||||||
|
return parsed_teams
|
||||||
|
|
||||||
|
def get_headers(
|
||||||
|
self,
|
||||||
|
) -> Mapping[str, Union[str, bytes]]:
|
||||||
|
if not isinstance(self.access_token, str):
|
||||||
|
raise TypeError(f"Access Token: {self.access_token}, must be str.")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": str(self.access_token),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def get_default_params(self) -> Dict:
|
||||||
|
return {"archived": "false"}
|
||||||
|
|
||||||
|
def get_authorized_teams(self) -> Dict[Any, Any]:
|
||||||
|
"""
|
||||||
|
Get all teams for the user
|
||||||
|
"""
|
||||||
|
url = f"{DEFAULT_URL}/team"
|
||||||
|
|
||||||
|
response = requests.get(url, headers=self.get_headers())
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
parsed_teams = self.attempt_parse_teams(data)
|
||||||
|
|
||||||
|
return parsed_teams
|
||||||
|
|
||||||
|
def get_folders(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Get all the folders for the team
|
||||||
|
"""
|
||||||
|
url = f"{DEFAULT_URL}/team/" + str(self.team_id) + "/space"
|
||||||
|
params = self.get_default_params()
|
||||||
|
response = requests.get(url, headers=self.get_headers(), params=params)
|
||||||
|
return {"response": response}
|
||||||
|
|
||||||
|
def get_task(self, query: str, fault_tolerant: bool = True) -> Dict:
|
||||||
|
"""
|
||||||
|
Retrieve a specific task
|
||||||
|
"""
|
||||||
|
|
||||||
|
params, error = load_query(query, fault_tolerant=True)
|
||||||
|
if params is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
url = f"{DEFAULT_URL}/task/{params['task_id']}"
|
||||||
|
params = {
|
||||||
|
"custom_task_ids": "true",
|
||||||
|
"team_id": self.team_id,
|
||||||
|
"include_subtasks": "true",
|
||||||
|
}
|
||||||
|
response = requests.get(url, headers=self.get_headers(), params=params)
|
||||||
|
data = response.json()
|
||||||
|
parsed_task = parse_dict_through_component(
|
||||||
|
data, Task, fault_tolerant=fault_tolerant
|
||||||
|
)
|
||||||
|
|
||||||
|
return parsed_task
|
||||||
|
|
||||||
|
def get_lists(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Get all available lists
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = f"{DEFAULT_URL}/folder/{self.folder_id}/list"
|
||||||
|
params = self.get_default_params()
|
||||||
|
response = requests.get(url, headers=self.get_headers(), params=params)
|
||||||
|
return {"response": response}
|
||||||
|
|
||||||
|
def query_tasks(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Query tasks that match certain fields
|
||||||
|
"""
|
||||||
|
params, error = load_query(query, fault_tolerant=True)
|
||||||
|
if params is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
url = f"{DEFAULT_URL}/list/{params['list_id']}/task"
|
||||||
|
|
||||||
|
params = self.get_default_params()
|
||||||
|
response = requests.get(url, headers=self.get_headers(), params=params)
|
||||||
|
|
||||||
|
return {"response": response}
|
||||||
|
|
||||||
|
def get_spaces(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Get all spaces for the team
|
||||||
|
"""
|
||||||
|
url = f"{DEFAULT_URL}/team/{self.team_id}/space"
|
||||||
|
response = requests.get(
|
||||||
|
url, headers=self.get_headers(), params=self.get_default_params()
|
||||||
|
)
|
||||||
|
data = response.json()
|
||||||
|
parsed_spaces = parse_dict_through_component(data, Space, fault_tolerant=True)
|
||||||
|
return parsed_spaces
|
||||||
|
|
||||||
|
def get_task_attribute(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Update an attribute of a specified task
|
||||||
|
"""
|
||||||
|
|
||||||
|
task = self.get_task(query, fault_tolerant=True)
|
||||||
|
params, error = load_query(query, fault_tolerant=True)
|
||||||
|
if not isinstance(params, dict):
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
if params["attribute_name"] not in task:
|
||||||
|
return {
|
||||||
|
"Error": f"""attribute_name = {params['attribute_name']} was not
|
||||||
|
found in task keys {task.keys()}. Please call again with one of the key names."""
|
||||||
|
}
|
||||||
|
|
||||||
|
return {params["attribute_name"]: task[params["attribute_name"]]}
|
||||||
|
|
||||||
|
def update_task(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Update an attribute of a specified task
|
||||||
|
"""
|
||||||
|
query_dict, error = load_query(query, fault_tolerant=True)
|
||||||
|
if query_dict is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
url = f"{DEFAULT_URL}/task/{query_dict['task_id']}"
|
||||||
|
params = {
|
||||||
|
"custom_task_ids": "true",
|
||||||
|
"team_id": self.team_id,
|
||||||
|
"include_subtasks": "true",
|
||||||
|
}
|
||||||
|
headers = self.get_headers()
|
||||||
|
payload = {query_dict["attribute_name"]: query_dict["value"]}
|
||||||
|
|
||||||
|
response = requests.put(url, headers=headers, params=params, json=payload)
|
||||||
|
|
||||||
|
return {"response": response}
|
||||||
|
|
||||||
|
def update_task_assignees(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Add or remove assignees of a specified task
|
||||||
|
"""
|
||||||
|
query_dict, error = load_query(query, fault_tolerant=True)
|
||||||
|
if query_dict is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
for user in query_dict["users"]:
|
||||||
|
if not isinstance(user, int):
|
||||||
|
return {
|
||||||
|
"Error": f"""All users must be integers, not strings!
|
||||||
|
"Got user {user} if type {type(user)}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{DEFAULT_URL}/task/{query_dict['task_id']}"
|
||||||
|
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
if query_dict["operation"] == "add":
|
||||||
|
assigne_payload = {"add": query_dict["users"], "rem": []}
|
||||||
|
elif query_dict["operation"] == "rem":
|
||||||
|
assigne_payload = {"add": [], "rem": query_dict["users"]}
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid operation ({query_dict['operation']}). ",
|
||||||
|
"Valid options ['add', 'rem'].",
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"custom_task_ids": "true",
|
||||||
|
"team_id": self.team_id,
|
||||||
|
"include_subtasks": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {"assignees": assigne_payload}
|
||||||
|
response = requests.put(url, headers=headers, params=params, json=payload)
|
||||||
|
return {"response": response}
|
||||||
|
|
||||||
|
def create_task(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Creates a new task
|
||||||
|
"""
|
||||||
|
query_dict, error = load_query(query, fault_tolerant=True)
|
||||||
|
if query_dict is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
list_id = self.list_id
|
||||||
|
url = f"{DEFAULT_URL}/list/{list_id}/task"
|
||||||
|
params = {"custom_task_ids": "true", "team_id": self.team_id}
|
||||||
|
|
||||||
|
payload = extract_dict_elements_from_component_fields(query_dict, Task)
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
response = requests.post(url, json=payload, headers=headers, params=params)
|
||||||
|
data: Dict = response.json()
|
||||||
|
return parse_dict_through_component(data, Task, fault_tolerant=True)
|
||||||
|
|
||||||
|
def create_list(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Creates a new list
|
||||||
|
"""
|
||||||
|
query_dict, error = load_query(query, fault_tolerant=True)
|
||||||
|
if query_dict is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
# Default to using folder as location if it exists.
|
||||||
|
# If not, fall back to using the space.
|
||||||
|
location = self.folder_id if self.folder_id else self.space_id
|
||||||
|
url = f"{DEFAULT_URL}/folder/{location}/list"
|
||||||
|
|
||||||
|
payload = extract_dict_elements_from_component_fields(query_dict, Task)
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
response = requests.post(url, json=payload, headers=headers)
|
||||||
|
data = response.json()
|
||||||
|
parsed_list = parse_dict_through_component(data, CUList, fault_tolerant=True)
|
||||||
|
# set list id to new list
|
||||||
|
if "id" in parsed_list:
|
||||||
|
self.list_id = parsed_list["id"]
|
||||||
|
return parsed_list
|
||||||
|
|
||||||
|
def create_folder(self, query: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Creates a new folder
|
||||||
|
"""
|
||||||
|
|
||||||
|
query_dict, error = load_query(query, fault_tolerant=True)
|
||||||
|
if query_dict is None:
|
||||||
|
return {"Error": error}
|
||||||
|
|
||||||
|
space_id = self.space_id
|
||||||
|
url = f"{DEFAULT_URL}/space/{space_id}/folder"
|
||||||
|
payload = {
|
||||||
|
"name": query_dict["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
response = requests.post(url, json=payload, headers=headers)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if "id" in data:
|
||||||
|
self.list_id = data["id"]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def run(self, mode: str, query: str) -> str:
|
||||||
|
if mode == "get_task":
|
||||||
|
output = self.get_task(query)
|
||||||
|
elif mode == "get_task_attribute":
|
||||||
|
output = self.get_task_attribute(query)
|
||||||
|
elif mode == "get_teams":
|
||||||
|
output = self.get_authorized_teams()
|
||||||
|
elif mode == "create_task":
|
||||||
|
output = self.create_task(query)
|
||||||
|
elif mode == "create_list":
|
||||||
|
output = self.create_list(query)
|
||||||
|
elif mode == "create_folder":
|
||||||
|
output = self.create_folder(query)
|
||||||
|
elif mode == "get_lists":
|
||||||
|
output = self.get_lists()
|
||||||
|
elif mode == "get_folders":
|
||||||
|
output = self.get_folders()
|
||||||
|
elif mode == "get_spaces":
|
||||||
|
output = self.get_spaces()
|
||||||
|
elif mode == "update_task":
|
||||||
|
output = self.update_task(query)
|
||||||
|
elif mode == "update_task_assignees":
|
||||||
|
output = self.update_task_assignees(query)
|
||||||
|
else:
|
||||||
|
output = {"ModeError": f"Got unexpected mode {mode}."}
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.dumps(output)
|
||||||
|
except Exception:
|
||||||
|
return str(output)
|
@ -1,6 +1,6 @@
|
|||||||
# openai
|
# openai
|
||||||
# your api key from https://platform.openai.com/account/api-keys
|
# your api key from https://platform.openai.com/account/api-keys
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
|
|
||||||
|
|
||||||
# searchapi
|
# searchapi
|
||||||
@ -22,6 +22,8 @@ PINECONE_ENVIRONMENT=us-west4-gcp
|
|||||||
# JIRA_USERNAME=your_jira_username_here
|
# JIRA_USERNAME=your_jira_username_here
|
||||||
# JIRA_INSTANCE_URL=your_jira_instance_url_here
|
# JIRA_INSTANCE_URL=your_jira_instance_url_here
|
||||||
|
|
||||||
|
# clickup
|
||||||
|
CLICKUP_ACCESS_TOKEN=your_clickup_access_token
|
||||||
|
|
||||||
# power bi
|
# power bi
|
||||||
# sign in to azure in order to authenticate with DefaultAzureCredentials
|
# sign in to azure in order to authenticate with DefaultAzureCredentials
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
"""Integration test for JIRA API Wrapper."""
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from langchain.utilities.clickup import ClickupAPIWrapper
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def clickup_wrapper() -> ClickupAPIWrapper:
|
||||||
|
return ClickupAPIWrapper()
|
||||||
|
|
||||||
|
|
||||||
|
def test_init(clickup_wrapper: ClickupAPIWrapper) -> None:
|
||||||
|
assert isinstance(clickup_wrapper, ClickupAPIWrapper)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_access_code_url() -> None:
|
||||||
|
assert isinstance(
|
||||||
|
ClickupAPIWrapper.get_access_code_url("oauth_client_id", "oauth_client_secret"),
|
||||||
|
str,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_access_token() -> None:
|
||||||
|
output = ClickupAPIWrapper.get_access_token(
|
||||||
|
"oauth_client_id", "oauth_client_secret", "code"
|
||||||
|
)
|
||||||
|
assert output is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_related(clickup_wrapper: ClickupAPIWrapper) -> None:
|
||||||
|
time_str = datetime.now().strftime("%d/%m/%Y-%H:%M:%S")
|
||||||
|
task_name = f"Test Folder - {time_str}"
|
||||||
|
|
||||||
|
# Create Folder
|
||||||
|
create_response = json.loads(
|
||||||
|
clickup_wrapper.run(mode="create_folder", query=json.dumps({"name": task_name}))
|
||||||
|
)
|
||||||
|
assert create_response["name"] == task_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_related(clickup_wrapper: ClickupAPIWrapper) -> None:
|
||||||
|
time_str = datetime.now().strftime("%d/%m/%Y-%H:%M:%S")
|
||||||
|
task_name = f"Test List - {time_str}"
|
||||||
|
|
||||||
|
# Create List
|
||||||
|
create_response = json.loads(
|
||||||
|
clickup_wrapper.run(mode="create_list", query=json.dumps({"name": task_name}))
|
||||||
|
)
|
||||||
|
assert create_response["name"] == task_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_related(clickup_wrapper: ClickupAPIWrapper) -> None:
|
||||||
|
time_str = datetime.now().strftime("%d/%m/%Y-%H:%M:%S")
|
||||||
|
task_name = f"Test Task - {time_str}"
|
||||||
|
|
||||||
|
# Create task
|
||||||
|
create_response = json.loads(
|
||||||
|
clickup_wrapper.run(
|
||||||
|
mode="create_task",
|
||||||
|
query=json.dumps({"name": task_name, "description": "This is a Test"}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert create_response["name"] == task_name
|
||||||
|
|
||||||
|
# Get task
|
||||||
|
task_id = create_response["id"]
|
||||||
|
get_response = json.loads(
|
||||||
|
clickup_wrapper.run(mode="get_task", query=json.dumps({"task_id": task_id}))
|
||||||
|
)
|
||||||
|
|
||||||
|
assert get_response["name"] == task_name
|
||||||
|
|
||||||
|
# Update task
|
||||||
|
new_name = f"{task_name} - New"
|
||||||
|
clickup_wrapper.run(
|
||||||
|
mode="update_task",
|
||||||
|
query=json.dumps(
|
||||||
|
{"task_id": task_id, "attribute_name": "name", "value": new_name}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
get_response_2 = json.loads(
|
||||||
|
clickup_wrapper.run(mode="get_task", query=json.dumps({"task_id": task_id}))
|
||||||
|
)
|
||||||
|
assert get_response_2["name"] == new_name
|
Loading…
Reference in New Issue
Block a user