From 76cc19f525bb1bc07eadfc35650c5dd8dfebe20a Mon Sep 17 00:00:00 2001 From: "Charlie.Wei" Date: Tue, 30 Jan 2024 11:03:20 +0800 Subject: [PATCH] Add custom tools (#2259) Co-authored-by: luowei Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> --- api/core/tools/provider/builtin/_positions.py | 5 +- .../provider/builtin/gaode/_assets/icon.png | Bin 0 -> 1719 bytes .../tools/provider/builtin/gaode/gaode.py | 24 +++++++ .../tools/provider/builtin/gaode/gaode.yaml | 29 +++++++++ .../builtin/gaode/tools/gaode_weather.py | 55 ++++++++++++++++ .../builtin/gaode/tools/gaode_weather.yaml | 28 ++++++++ .../provider/builtin/github/_assets/icon.png | Bin 0 -> 1443 bytes .../tools/provider/builtin/github/github.py | 31 +++++++++ .../tools/provider/builtin/github/github.yaml | 46 +++++++++++++ .../builtin/github/tools/repositories.py | 61 ++++++++++++++++++ .../builtin/github/tools/repositories.yaml | 42 ++++++++++++ 11 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 api/core/tools/provider/builtin/gaode/_assets/icon.png create mode 100644 api/core/tools/provider/builtin/gaode/gaode.py create mode 100644 api/core/tools/provider/builtin/gaode/gaode.yaml create mode 100644 api/core/tools/provider/builtin/gaode/tools/gaode_weather.py create mode 100644 api/core/tools/provider/builtin/gaode/tools/gaode_weather.yaml create mode 100644 api/core/tools/provider/builtin/github/_assets/icon.png create mode 100644 api/core/tools/provider/builtin/github/github.py create mode 100644 api/core/tools/provider/builtin/github/github.yaml create mode 100644 api/core/tools/provider/builtin/github/tools/repositories.py create mode 100644 api/core/tools/provider/builtin/github/tools/repositories.yaml diff --git a/api/core/tools/provider/builtin/_positions.py b/api/core/tools/provider/builtin/_positions.py index b7c2b80187..b8b92e3129 100644 --- a/api/core/tools/provider/builtin/_positions.py +++ b/api/core/tools/provider/builtin/_positions.py @@ -13,8 +13,11 @@ position = { 'stablediffusion': 9, 'vectorizer': 10, 'youtube': 11, + 'github': 12, + 'gaode': 13 } + class BuiltinToolProviderSort: @staticmethod def sort(providers: List[UserToolProvider]) -> List[UserToolProvider]: @@ -23,4 +26,4 @@ class BuiltinToolProviderSort: sorted_providers = sorted(providers, key=sort_compare) - return sorted_providers \ No newline at end of file + return sorted_providers diff --git a/api/core/tools/provider/builtin/gaode/_assets/icon.png b/api/core/tools/provider/builtin/gaode/_assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d4aec4cda8b3934d0f8eec7274da96d24464b53b GIT binary patch literal 1719 zcmV;o21xmdP)Px*aY;l$RCr$PU2SsXAPlve*>kl$P|ncJ8G4|m=h|ty!-pM%0SS-MF`ZKZrBKL2636rC#wdy!QlybRNxmJWC*am z-QKYtfg|A2!B?<|5}@PZbvnPiLw*1g@Ok^{GN?jN=6^3vfF%Kl4~fj+-`h03eC0l8 zx7RlzU_m1;NeKc5vHT`6AYg_O0|*l^itKE0XzBs+n6M{cgn%2;!R7?$LG991mw?Z~ zbiXSPSO?>f;1MvG?)OQ6lM>$v7}@%p%mb47;OYe%0tUAJ{s>sTzz{IH_4i1C4ak;& z!L46}096&uD#L3TvIPA6b~}Tm8W>+1deLhG(~DXI0oD|}hdH0-`4IBM;{hg+>UonQ zKz*`HH<#HokjWgCRZ6x)=K=RTegR*tjFnXn6afd&|7s7=`u^=Kwrs)IiA)iWPXbI9 z7*&Cb{@!2Z(qM0vA&axf`TF#LF!(00`it%>&|Z1Dj(`JXK)wXUHx$IzbXTGRwf!>r z(axoU@c1d{LO@DtQj6*L`@#fOV0at?!WkF5iva|`u7Z3-0>XhcL=Xf%K^f;r1mJ;p z)5^4$ROtUvVgM{NTkCf-8u;D{{U4nG7(8Eg+#n6#Uz~p4J04MjVBpevufD~#K~ToA z#W;$h%;_Vu9BVtZn_E#sNcMpPjS74xPUJj;I{h?o9x^%HJK2$^?I#|Bplf1bpW9@1O(k`^W>|FW{w0-GGAh;Q^}D zYd*0RrF?*54^9ETum6Z$(6QlkKtR$&=z#aF)^uCmK7sz5c>vX?az^M4qZ}A+%hyEG z=)WOAH%9R(O+(kd>jP^P{M~9*9l?&V-M}AVq|ohQBE|@Ouu@qEzN7#3hTD>WdLUBI zA6x07$)d>CP+Abc3w$`(!V^BMF%#5(wp~dQzz;k(_{8#KgD~fkWMDf7*3wF@3(5}e z*NW)wXtO=GHDSKMiqCZDWM|uFW&PhDKD7jew{xJ)tKh<@CKFr!F8?nWYv2|t0%Y{v ze}HJ+hyi>C{ilclZGevkTm49=U#J(^FR}jD5YP_zX5iOtKZy~ZBSG?kH8kp|6*5{r zlm2IVK;5}GMK14TACLu>FN&bX#vnhL1gyi9mmhHPn;Cqj09;Cd33L^wM4>Ehxu^^9 zA@9q2BIn|q7!c}(*Bg2OpHcrSJYdTOlQ;ANKBN2)5wMU!n1z4=PeAjX1c0wzRx3fY zvVY4 z!w~}_j~ocn-Gk4o|3V(HlZmirmq-KvCkf(YgyNT<&dn_q8E2W0AVeO0vqQ1e;Hy& z1T-i!#E23|Kl>w~EBTSp|Biw%AU{Tr-4m-QWG3nXMW?I{;T2M-WwlZpMB#c1iD0e1EHBT2t++ge%{IoZ4r@$e{bZ6?%mwAdx z@4oQ(e4VW_QV|lutA2?WIu8~P=-#3P2mIg_;UeMlmF6NIQ1VLiH9ak^&lg$>(``*W z0Ioyfj;$2YNG9NyX5|S$Rbp`jXg6(@wh<8{O8^RdcP5N|&PVkI83NG2uT?25?z`(A z%_G3?q~CTnkZ2&Di3 N002ovPDHLkV1kusJ5B%q literal 0 HcmV?d00001 diff --git a/api/core/tools/provider/builtin/gaode/gaode.py b/api/core/tools/provider/builtin/gaode/gaode.py new file mode 100644 index 0000000000..50433fbc9e --- /dev/null +++ b/api/core/tools/provider/builtin/gaode/gaode.py @@ -0,0 +1,24 @@ +import requests +import urllib.parse +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController +from core.tools.errors import ToolProviderCredentialValidationError + + +class GaodeProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict) -> None: + try: + if 'api_key' not in credentials or not credentials.get('api_key'): + raise ToolProviderCredentialValidationError("Gaode API key is required.") + + try: + response = requests.get(url="https://restapi.amap.com/v3/geocode/geo?address={address}&key={apikey}" + "".format(address=urllib.parse.quote('广东省广州市天河区广州塔'), + apikey=credentials.get('api_key'))) + if response.status_code == 200 and (response.json()).get('info') == 'OK': + pass + else: + raise ToolProviderCredentialValidationError((response.json()).get('info')) + except Exception as e: + raise ToolProviderCredentialValidationError("Gaode API Key is invalid. {}".format(e)) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) diff --git a/api/core/tools/provider/builtin/gaode/gaode.yaml b/api/core/tools/provider/builtin/gaode/gaode.yaml new file mode 100644 index 0000000000..870c9e746d --- /dev/null +++ b/api/core/tools/provider/builtin/gaode/gaode.yaml @@ -0,0 +1,29 @@ +identity: + author: CharlirWei + name: gaode + label: + en_US: GaoDe + zh_Hans: 高德 + pt_BR: GaoDe + description: + en_US: Autonavi Open Platform service toolkit. + zh_Hans: 高德开放平台服务工具包。 + pt_BR: Kit de ferramentas de serviço Autonavi Open Platform. + icon: icon.png +credentials_for_provider: + api_key: + type: secret-input + required: true + label: + en_US: API Key + zh_Hans: API Key + pt_BR: Fogo a chave + placeholder: + en_US: Please enter your GaoDe API Key + zh_Hans: 请输入你的高德开放平台 API Key + pt_BR: Insira sua chave de API GaoDe + help: + en_US: Get your API Key from GaoDe + zh_Hans: 从高德获取您的 API Key + pt_BR: Obtenha sua chave de API do GaoDe + url: https://console.amap.com/dev/key/app diff --git a/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py b/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py new file mode 100644 index 0000000000..4147a2c564 --- /dev/null +++ b/api/core/tools/provider/builtin/gaode/tools/gaode_weather.py @@ -0,0 +1,55 @@ +import json +import requests +from core.tools.tool.builtin_tool import BuiltinTool +from core.tools.entities.tool_entities import ToolInvokeMessage +from typing import Any, Dict, List, Union + + +class GaodeRepositoriesTool(BuiltinTool): + def _invoke(self, user_id: str, tool_paramters: Dict[str, Any]) -> Union[ToolInvokeMessage, List[ToolInvokeMessage]]: + """ + invoke tools + """ + city = tool_paramters.get('city', '') + if not city: + return self.create_text_message('Please tell me your city') + + if 'api_key' not in self.runtime.credentials or not self.runtime.credentials.get('api_key'): + return self.create_text_message("Gaode API key is required.") + + try: + s = requests.session() + api_domain = 'https://restapi.amap.com/v3' + city_response = s.request(method='GET', headers={"Content-Type": "application/json; charset=utf-8"}, + url="{url}/config/district?keywords={keywords}" + "&subdistrict=0&extensions=base&key={apikey}" + "".format(url=api_domain, keywords=city, + apikey=self.runtime.credentials.get('api_key'))) + City_data = city_response.json() + if city_response.status_code == 200 and City_data.get('info') == 'OK': + if len(City_data.get('districts')) > 0: + CityCode = City_data['districts'][0]['adcode'] + weatherInfo_response = s.request(method='GET', + url="{url}/weather/weatherInfo?city={citycode}&extensions=all&key={apikey}&output=json" + "".format(url=api_domain, citycode=CityCode, + apikey=self.runtime.credentials.get('api_key'))) + weatherInfo_data = weatherInfo_response.json() + if weatherInfo_response.status_code == 200 and weatherInfo_data.get('info') == 'OK': + contents = list() + if len(weatherInfo_data.get('forecasts')) > 0: + for item in weatherInfo_data['forecasts'][0]['casts']: + content = dict() + content['date'] = item.get('date') + content['week'] = item.get('week') + content['dayweather'] = item.get('dayweather') + content['daytemp_float'] = item.get('daytemp_float') + content['daywind'] = item.get('daywind') + content['nightweather'] = item.get('nightweather') + content['nighttemp_float'] = item.get('nighttemp_float') + contents.append(content) + s.close() + return self.create_text_message(self.summary(user_id=user_id, content=json.dumps(contents, ensure_ascii=False))) + s.close() + return self.create_text_message(f'No weather information for {city} was found.') + except Exception as e: + return self.create_text_message("Github API Key and Api Version is invalid. {}".format(e)) diff --git a/api/core/tools/provider/builtin/gaode/tools/gaode_weather.yaml b/api/core/tools/provider/builtin/gaode/tools/gaode_weather.yaml new file mode 100644 index 0000000000..e41851e188 --- /dev/null +++ b/api/core/tools/provider/builtin/gaode/tools/gaode_weather.yaml @@ -0,0 +1,28 @@ +identity: + name: gaode_weather + author: CharlieWei + label: + en_US: Weather Forecast + zh_Hans: 天气预报 + pt_BR: Previsão do tempo + icon: icon.svg +description: + human: + en_US: Weather forecast inquiry + zh_Hans: 天气预报查询。 + pt_BR: Inquérito sobre previsão meteorológica. + llm: A tool when you want to ask about the weather or weather-related question. +parameters: + - name: city + type: string + required: true + label: + en_US: city + zh_Hans: 城市 + pt_BR: cidade + human_description: + en_US: Target city for weather forecast query. + zh_Hans: 天气预报查询的目标城市。 + pt_BR: Cidade de destino para consulta de previsão do tempo. + llm_description: If you don't know you can extract the city name from the question or you can reply:Please tell me your city. You have to extract the Chinese city name from the question. + form: llm diff --git a/api/core/tools/provider/builtin/github/_assets/icon.png b/api/core/tools/provider/builtin/github/_assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1615e8914ab7a1d66224208dc3cf8c85a0f9f73 GIT binary patch literal 1443 zcmV;U1zh@xP)Px)T}ebiRCr$PoC~hhFbqY9l>sXQRtBsLFpQ-Twej^YPTP!x;62iO?(ucpRMQWy z{V7%8L+R*y@Ye=V90VJ{22iX(@$lOL8$huF#lx@efKRX2i~jKE_>HM_jmNcu@R13fo6;A`8a)AbbH6oq{K;;4r0PV)x=7uMwAX+!<0u2C5?q>+_ zgwis)zGIK$VHcnPypy>i0FUEg7oY&VlYt{bAE;?pc-IUC9SXpce&A?GlhW3)wc=|> z6ZI7vyw7Sp&v%i#V4FnwS9u|bEe# zDxeMEUyKY>u_Tfez@73xTg~c#4In64S%YzS3?n%pg?f-6v%9$zfGZxpNkBIMNulKb zw`PNq6m&-iYyim-KI-0cBH(g>C-2?qPS^vWqzonM;!$j2L~Dok1Y#M0JKcv;eX^pszBaDgZN(E7^(T-gQQ3?K%ebQVM%q)$Ryl1yyx zb-!4M%mN@zWTL3(P~OxdrXSHB@0ofxAztv6Qc;BXVE`gF;wwRjNV#J9$$}s$C~619yZ#zzTJy;j@nXk%(iw=zg zBp0-nYrmz4etnelX2u(Dc@Fe9y?s|RCreg?(hqEtW(JZ~`$~rv%8sxs1gazk9Yiyr|1O+`{V;c}Hr)@bo*o*4k#WwIm;niHkX zb%g`CXaH{Uqa47HBnZ)!4H_KWE)qb9_|X8+bVTj=O;|^%hD+AyG!z9OO#BQ08VI8Z zHkWmSP9p$>ik}4_0>X*_!o|-7Kz$=6>RG_92EbKnj;tP41)YtdeK^DYtO6h<>RYk0 z0q|H_5Gw#k7e6}%9k~K2Rk=zszmNIEj_4*lMR=a$EC;ljAa1B|06&+GE`NvjD-{5c zv@{dI)QInCR00^eBBSc)(_g9Zl_^MjgmCNjt2p4+B3IrAJXTnA;oAYIQc3MLfFxnj xZZ`l_sibxrK$5U%w;KSeR8qSQAW2xX+ut3fJ#Z8eaNz&|002ovPDHLkV1ifje%$~7 literal 0 HcmV?d00001 diff --git a/api/core/tools/provider/builtin/github/github.py b/api/core/tools/provider/builtin/github/github.py new file mode 100644 index 0000000000..8376093bfe --- /dev/null +++ b/api/core/tools/provider/builtin/github/github.py @@ -0,0 +1,31 @@ +import requests +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController +from core.tools.errors import ToolProviderCredentialValidationError + + +class GihubProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict) -> None: + try: + if 'access_tokens' not in credentials or not credentials.get('access_tokens'): + raise ToolProviderCredentialValidationError("Github API Access Tokens is required.") + if 'api_version' not in credentials or not credentials.get('api_version'): + api_version = '2022-11-28' + else: + api_version = credentials.get('api_version') + + try: + headers = { + "Content-Type": "application/vnd.github+json", + "Authorization": f"Bearer {credentials.get('access_tokens')}", + "X-GitHub-Api-Version": api_version + } + + response = requests.get( + url="https://api.github.com/search/users?q={account}".format(account='charli117'), + headers=headers) + if response.status_code != 200: + raise ToolProviderCredentialValidationError((response.json()).get('message')) + except Exception as e: + raise ToolProviderCredentialValidationError("Github API Key and Api Version is invalid. {}".format(e)) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) diff --git a/api/core/tools/provider/builtin/github/github.yaml b/api/core/tools/provider/builtin/github/github.yaml new file mode 100644 index 0000000000..ef0b01ed5b --- /dev/null +++ b/api/core/tools/provider/builtin/github/github.yaml @@ -0,0 +1,46 @@ +identity: + author: CharlirWei + name: github + label: + en_US: Github + zh_Hans: Github + pt_BR: Github + description: + en_US: GitHub is an online software source code hosting service. + zh_Hans: GitHub是一个在线软件源代码托管服务平台。 + pt_BR: GitHub é uma plataforma online para serviços de hospedagem de código fonte de software. + icon: icon.png +credentials_for_provider: + access_tokens: + type: secret-input + required: true + label: + en_US: Access Tokens + zh_Hans: Access Tokens + pt_BR: Tokens de acesso + placeholder: + en_US: Please input your Github Access Tokens + zh_Hans: 请输入你的 Github Access Tokens + pt_BR: Insira seus Tokens de Acesso do Github + help: + en_US: Get your Access Tokens from Github + zh_Hans: 从 Github 获取您的 Access Tokens + pt_BR: Obtenha sua chave da API do Google no Google + url: https://github.com/settings/tokens?type=beta + api_version: + type: text-input + required: false + default: '2022-11-28' + label: + en_US: API Version + zh_Hans: API Version + pt_BR: Versão da API + placeholder: + en_US: Please input your Github API Version + zh_Hans: 请输入你的 Github API Version + pt_BR: Insira sua versão da API do Github + help: + en_US: Get your API Version from Github + zh_Hans: 从 Github 获取您的 API Version + pt_BR: Obtenha sua versão da API do Github + url: https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28 diff --git a/api/core/tools/provider/builtin/github/tools/repositories.py b/api/core/tools/provider/builtin/github/tools/repositories.py new file mode 100644 index 0000000000..9847b54f04 --- /dev/null +++ b/api/core/tools/provider/builtin/github/tools/repositories.py @@ -0,0 +1,61 @@ +import json +import requests +from datetime import datetime +from urllib.parse import quote +from core.tools.tool.builtin_tool import BuiltinTool +from core.tools.entities.tool_entities import ToolInvokeMessage + +from typing import Any, Dict, List, Union + + +class GihubRepositoriesTool(BuiltinTool): + def _invoke(self, user_id: str, tool_paramters: Dict[str, Any]) -> Union[ToolInvokeMessage, List[ToolInvokeMessage]]: + """ + invoke tools + """ + top_n = tool_paramters.get('top_n', 5) + query = tool_paramters.get('query', '') + if not query: + return self.create_text_message('Please input symbol') + + if 'access_tokens' not in self.runtime.credentials or not self.runtime.credentials.get('access_tokens'): + return self.create_text_message("Github API Access Tokens is required.") + if 'api_version' not in self.runtime.credentials or not self.runtime.credentials.get('api_version'): + api_version = '2022-11-28' + else: + api_version = self.runtime.credentials.get('api_version') + + try: + headers = { + "Content-Type": "application/vnd.github+json", + "Authorization": f"Bearer {self.runtime.credentials.get('access_tokens')}", + "X-GitHub-Api-Version": api_version + } + s = requests.session() + api_domain = 'https://api.github.com' + response = s.request(method='GET', headers=headers, + url=f"{api_domain}/search/repositories?" + f"q={quote(query)}&sort=stars&per_page={top_n}&order=desc") + response_data = response.json() + if response.status_code == 200 and isinstance(response_data.get('items'), list): + contents = list() + if len(response_data.get('items')) > 0: + for item in response_data.get('items'): + content = dict() + updated_at_object = datetime.strptime(item['updated_at'], "%Y-%m-%dT%H:%M:%SZ") + content['owner'] = item['owner']['login'] + content['name'] = item['name'] + content['description'] = item['description'][:100] + '...' if len(item['description']) > 100 else item['description'] + content['url'] = item['html_url'] + content['star'] = item['watchers'] + content['forks'] = item['forks'] + content['updated'] = updated_at_object.strftime("%Y-%m-%d") + contents.append(content) + s.close() + return self.create_text_message(self.summary(user_id=user_id, content=json.dumps(contents, ensure_ascii=False))) + else: + return self.create_text_message(f'No items related to {query} were found.') + else: + return self.create_text_message((response.json()).get('message')) + except Exception as e: + return self.create_text_message("Github API Key and Api Version is invalid. {}".format(e)) diff --git a/api/core/tools/provider/builtin/github/tools/repositories.yaml b/api/core/tools/provider/builtin/github/tools/repositories.yaml new file mode 100644 index 0000000000..e11e50a35d --- /dev/null +++ b/api/core/tools/provider/builtin/github/tools/repositories.yaml @@ -0,0 +1,42 @@ +identity: + name: repositories + author: CharlieWei + label: + en_US: Search Repositories + zh_Hans: 仓库搜索 + pt_BR: Pesquisar Repositórios + icon: icon.svg +description: + human: + en_US: Search the Github repository to retrieve the open source projects you need + zh_Hans: 搜索Github仓库,检索你需要的开源项目。 + pt_BR: Pesquise o repositório do Github para recuperar os projetos de código aberto necessários. + llm: A tool when you wants to search for popular warehouses or open source projects for any keyword. format query condition like "keywords+language:js", language can be other dev languages. +parameters: + - name: query + type: string + required: true + label: + en_US: query + zh_Hans: 关键字 + pt_BR: consulta + human_description: + en_US: You want to find the project development language, keywords, For example. Find 10 Python developed PDF document parsing projects. + zh_Hans: 你想要找的项目开发语言、关键字,如:找10个Python开发的PDF文档解析项目。 + pt_BR: Você deseja encontrar a linguagem de desenvolvimento do projeto, palavras-chave, Por exemplo. Encontre 10 projetos de análise de documentos PDF desenvolvidos em Python. + llm_description: The query of you want to search, format query condition like "keywords+language:js", language can be other dev languages, por exemplo. Procuro um projeto de análise de documentos PDF desenvolvido em Python. + form: llm + - name: top_n + type: number + default: 5 + required: true + label: + en_US: Top N + zh_Hans: Top N + pt_BR: Topo N + human_description: + en_US: Number of records returned by sorting based on stars. 5 is returned by default. + zh_Hans: 基于stars排序返回的记录数, 默认返回5条。 + pt_BR: Número de registros retornados por classificação com base em estrelas. 5 é retornado por padrão. + llm_description: Extract the first N records from the returned result. + form: llm