From 23fe1dc960819e4a30557b5d6204a6e4752c0acb Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Mon, 25 Sep 2023 10:57:37 -0700 Subject: [PATCH 01/30] feat: code runner (wip) --- chat/display_widgets.py | 3 +++ knowledge_base/widgets.yaml | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 2d7fb05..53d5840 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -201,6 +201,9 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'withdraw-vault': items = params.split(",") lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") + elif command == 'code-runner': + items = params.split(",") + lines.append(f"supply the user with valid javascript code to run and perform their desicred action.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index acb20f3..f27c2ea 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -854,4 +854,23 @@ - amount - vault type: object - return_value_description: '' \ No newline at end of file + return_value_description: '' +- _name_: display_code_runner + description: + You are an expert Web3 developer well versed in using Javascript to interact with the ecosystem, you will help the user perform actions based on their request by generating functional Javascript code + + # INSTRUCTIONS + - Assume user wallet already connected to browser so don't ask for private key or Infura project ID + - Print out transaction hash if applicable + - Always use ethers.js + - Assume we already have the provider and signer available, and can be passed to the generated code + - Your final output should be a JSON object with a code field which contains formatted JS code with comments + parameters: + properties: + user_wallet_address: + description: default to using the connected address + type: string + required: + - user_wallet_address + type: object + return_value_description: '' From c49a1e6328b2a0006a7b51cba78b15f94941e607 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Mon, 25 Sep 2023 11:00:57 -0700 Subject: [PATCH 02/30] fix: spelling --- chat/display_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 53d5840..c811904 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -203,7 +203,7 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") elif command == 'code-runner': items = params.split(",") - lines.append(f"supply the user with valid javascript code to run and perform their desicred action.") + lines.append(f"supply the user with valid javascript code to run and perform their desired action.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") From e31cd0a7311a7e1cf8f1e4b62a3f48ecfdfc80dc Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Mon, 25 Sep 2023 12:48:12 -0700 Subject: [PATCH 03/30] feat: config for generate_code --- chat/display_widgets.py | 2 +- eval/eval_widgets.txt | 1 + knowledge_base/widgets.yaml | 14 +++----------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index c811904..f0210a4 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -201,7 +201,7 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'withdraw-vault': items = params.split(",") lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") - elif command == 'code-runner': + elif command == 'generate-code': items = params.split(",") lines.append(f"supply the user with valid javascript code to run and perform their desired action.") else: diff --git a/eval/eval_widgets.txt b/eval/eval_widgets.txt index cccf34c..4181398 100644 --- a/eval/eval_widgets.txt +++ b/eval/eval_widgets.txt @@ -34,3 +34,4 @@ display_uniswap fetch_nfts_owned_by_address_or_domain fetch_nfts_owned_by_user fetch_link_suggestion +generate_code \ No newline at end of file diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index f27c2ea..565436e 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -855,16 +855,8 @@ - vault type: object return_value_description: '' -- _name_: display_code_runner - description: - You are an expert Web3 developer well versed in using Javascript to interact with the ecosystem, you will help the user perform actions based on their request by generating functional Javascript code - - # INSTRUCTIONS - - Assume user wallet already connected to browser so don't ask for private key or Infura project ID - - Print out transaction hash if applicable - - Always use ethers.js - - Assume we already have the provider and signer available, and can be passed to the generated code - - Your final output should be a JSON object with a code field which contains formatted JS code with comments +- _name_: display_generate_code + description: generate code for the user parameters: properties: user_wallet_address: @@ -873,4 +865,4 @@ required: - user_wallet_address type: object - return_value_description: '' + return_value_description: 'formatted javascript code in JSON format' From d237a5acd013fdf0fedf79b76b3e17bca54e73ce Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Mon, 25 Sep 2023 12:59:53 -0700 Subject: [PATCH 04/30] chore: index bump --- utils/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/constants.py b/utils/constants.py index 3c9fa71..dc37c82 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -53,7 +53,7 @@ WIDGET_INFO_TOKEN_LIMIT = 4000 # Widget Index -WIDGET_INDEX_NAME = "WidgetV25" +WIDGET_INDEX_NAME = "WidgetV26" def get_widget_index_name(): if env.is_local(): From 5390e5575b1937e77ddd0d58873d5bf17c8f3e9e Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Mon, 25 Sep 2023 13:03:57 -0700 Subject: [PATCH 05/30] feat: generate code tool into index_widget --- tools/index_widget.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/index_widget.py b/tools/index_widget.py index 6274088..c075146 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -390,6 +390,19 @@ def fn(token_handler): tool._run(query) return fn +@error_wrap +def generate_code(query: str) -> Callable: + def fn(token_handler): + tool = dict( + type="tools.index_generate_code.IndexGenerateCodeTool", + _streaming=True, + name="GenerateCode", + content_description="", # not used + ) + tool = streaming.get_streaming_tools([tool], token_handler)[0] + tool._run(query) + return fn + class ListContainer(ContainerMixin, list): def message_prefix(self) -> str: From d21799db4966b03c9f40860b66f31d8648aaf3e0 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Mon, 25 Sep 2023 13:14:32 -0700 Subject: [PATCH 06/30] feat: generate_code handler (wip) --- tools/index_generate_code.py | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tools/index_generate_code.py diff --git a/tools/index_generate_code.py b/tools/index_generate_code.py new file mode 100644 index 0000000..43bc04d --- /dev/null +++ b/tools/index_generate_code.py @@ -0,0 +1,64 @@ +from langchain.prompts import PromptTemplate +from langchain.chains import LLMChain + +import registry +import streaming +from .index_lookup import IndexLookupTool +from gpt_index.utils import ErrorToRetry, retry_on_exceptions_with_backoff +import utils.timing as timing + + +TEMPLATE = '''You are an expert Web3 developer well versed in using JS to interact with the ecosystem, you will help the user perform actions based on their request by generating functional JS code + +# INSTRUCTIONS +- Assume user wallet already connected to browser so don't ask for private key, Infura project ID, or any credentials +- Print out transaction hash +- Always use ethers.js +- Assume there is an ethers.js provider and signer available and can be provided to the function +- Your final output should be a JSON object with a code field which contains formatted JS code with comments + +# USER REQUEST EXAMPLE +write code to send 2 eth to 0x123 + +--- +{task_info} +--- + +User: {question} +Assistant:''' + + +@registry.register_class +class IndexGenerateCodeTool(IndexLookupTool): + """Tool for generating code to perform a user request.""" + + _chain: LLMChain + + def __init__( + self, + *args, + **kwargs + ) -> None: + prompt = PromptTemplate( + input_variables=["task_info", "question"], + template=TEMPLATE, + ) + new_token_handler = kwargs.get('new_token_handler') + chain = streaming.get_streaming_chain(prompt, new_token_handler) + super().__init__( + *args, + _chain=chain, + input_description="a request that the user wants to perform using code", + output_description="generated javascript code", + **kwargs + ) + + def _run(self, query: str) -> str: + """Query index and answer question using document chunks.""" + + timing.log('widget_index_lookup_done') + + self._chain.verbose = True + result = self._chain.run() + + return result.strip() From 0849d6d16e2685e71cd17adc7557fa7d62b771d8 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 07:48:46 -0700 Subject: [PATCH 07/30] fix: try to prioritize gen_code when code context --- knowledge_base/widgets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 565436e..407a3dd 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -856,7 +856,7 @@ type: object return_value_description: '' - _name_: display_generate_code - description: generate code for the user + description: generate code for the user; this widget should always be chosen for anything relating to code parameters: properties: user_wallet_address: From c4c8c78b122bff27b7b10e637690b60def55abdb Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 08:01:21 -0700 Subject: [PATCH 08/30] fix: no display --- knowledge_base/widgets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 407a3dd..569f044 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -855,7 +855,7 @@ - vault type: object return_value_description: '' -- _name_: display_generate_code +- _name_: generate_code description: generate code for the user; this widget should always be chosen for anything relating to code parameters: properties: From bb5d17171ab04a805bc5ba2a8815a64690435ce3 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 08:01:55 -0700 Subject: [PATCH 09/30] fix: remove gen code --- chat/display_widgets.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index f0210a4..2d7fb05 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -201,9 +201,6 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'withdraw-vault': items = params.split(",") lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") - elif command == 'generate-code': - items = params.split(",") - lines.append(f"supply the user with valid javascript code to run and perform their desired action.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") From daa23979beeec67efec833cbb55c191308bdda46 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 08:02:48 -0700 Subject: [PATCH 10/30] feat: add gen code --- tools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/__init__.py b/tools/__init__.py index 426db12..819929b 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -6,4 +6,5 @@ from . import index_app_info from . import index_api_tool from . import app_usage_guide -from . import index_link_suggestion \ No newline at end of file +from . import index_link_suggestion +from . import index_generate_code \ No newline at end of file From 557f143028aa63ccd5be2af09b292c704f3088c5 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 08:09:23 -0700 Subject: [PATCH 11/30] feat: gen code --- tools/index_widget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/index_widget.py b/tools/index_widget.py index c075146..37a1ece 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -250,6 +250,8 @@ def replace_match(m: re.Match) -> Union[str, Generator, Callable]: # return fetch_scraped_sites(*params) elif command == 'fetch-link-suggestion': return fetch_link_suggestion(*params) + elif command == 'generate-code': + return generate_code(*params) elif command == aave.AaveSupplyContractWorkflow.WORKFLOW_TYPE: return str(exec_aave_operation(*params, operation='supply')) elif command == aave.AaveBorrowContractWorkflow.WORKFLOW_TYPE: From 06ec848e71b7cce09d0e31d3b48b002c3aee54ee Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 08:19:14 -0700 Subject: [PATCH 12/30] fix: reinstate gen code --- chat/display_widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 2d7fb05..f0210a4 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -201,6 +201,9 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'withdraw-vault': items = params.split(",") lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") + elif command == 'generate-code': + items = params.split(",") + lines.append(f"supply the user with valid javascript code to run and perform their desired action.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") From ca0ba24ae77345861d0bb56431308b1917e10c7b Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 09:02:07 -0700 Subject: [PATCH 13/30] fix: don't inherit from index lookup (since we don't need to lookup anything) --- tools/index_generate_code.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/index_generate_code.py b/tools/index_generate_code.py index 43bc04d..a84e7eb 100644 --- a/tools/index_generate_code.py +++ b/tools/index_generate_code.py @@ -3,7 +3,6 @@ import registry import streaming -from .index_lookup import IndexLookupTool from gpt_index.utils import ErrorToRetry, retry_on_exceptions_with_backoff import utils.timing as timing @@ -29,7 +28,7 @@ @registry.register_class -class IndexGenerateCodeTool(IndexLookupTool): +class IndexGenerateCodeTool(): """Tool for generating code to perform a user request.""" _chain: LLMChain @@ -54,11 +53,15 @@ def __init__( ) def _run(self, query: str) -> str: - """Query index and answer question using document chunks.""" + + retry_on_exceptions_with_backoff( + lambda: self._index.similarity_search(query, k=self._top_k), + [ErrorToRetry(TypeError)], + ) timing.log('widget_index_lookup_done') self._chain.verbose = True result = self._chain.run() - return result.strip() + return result From 0ebfab0b44ec0d3d8401afc9c602095739fba868 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 09:02:28 -0700 Subject: [PATCH 14/30] fix: better return description --- knowledge_base/widgets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 569f044..83fe34d 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -865,4 +865,4 @@ required: - user_wallet_address type: object - return_value_description: 'formatted javascript code in JSON format' + return_value_description: 'message with formatted javascript code in a property called 'code' in JSON format' From 7a0e17e127fe728b241e6c62a7822162cad69a5a Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 09:32:46 -0700 Subject: [PATCH 15/30] fix: return value descript --- knowledge_base/widgets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 83fe34d..622e7a8 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -865,4 +865,4 @@ required: - user_wallet_address type: object - return_value_description: 'message with formatted javascript code in a property called 'code' in JSON format' + return_value_description: 'message with formatted javascript code in a property called code in JSON format' From c995addf907adaac8c2e237cf817cfd8be0c6658 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 09:48:53 -0700 Subject: [PATCH 16/30] fix: inherit from base tool --- tools/index_generate_code.py | 43 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tools/index_generate_code.py b/tools/index_generate_code.py index a84e7eb..0514116 100644 --- a/tools/index_generate_code.py +++ b/tools/index_generate_code.py @@ -1,10 +1,10 @@ from langchain.prompts import PromptTemplate from langchain.chains import LLMChain +from pydantic import Extra import registry import streaming -from gpt_index.utils import ErrorToRetry, retry_on_exceptions_with_backoff -import utils.timing as timing +from .base import BaseTool, BASE_TOOL_DESCRIPTION_TEMPLATE TEMPLATE = '''You are an expert Web3 developer well versed in using JS to interact with the ecosystem, you will help the user perform actions based on their request by generating functional JS code @@ -19,49 +19,52 @@ # USER REQUEST EXAMPLE write code to send 2 eth to 0x123 ---- -{task_info} ---- - User: {question} Assistant:''' @registry.register_class -class IndexGenerateCodeTool(): +class IndexGenerateCodeTool(BaseTool): """Tool for generating code to perform a user request.""" _chain: LLMChain + class Config: + """Configuration for this pydantic object.""" + extra = Extra.allow + def __init__( self, *args, **kwargs ) -> None: prompt = PromptTemplate( - input_variables=["task_info", "question"], + input_variables=["question"], template=TEMPLATE, ) new_token_handler = kwargs.get('new_token_handler') chain = streaming.get_streaming_chain(prompt, new_token_handler) + description=BASE_TOOL_DESCRIPTION_TEMPLATE.format( + tool_description="generate code based on the user query", + input_description="a standalone query where user wants to generate code to perform an action", + output_description="a message answer, along with the generated code with helpful comments", + ) + super().__init__( *args, _chain=chain, - input_description="a request that the user wants to perform using code", - output_description="generated javascript code", + description=description, **kwargs ) def _run(self, query: str) -> str: - - retry_on_exceptions_with_backoff( - lambda: self._index.similarity_search(query, k=self._top_k), - [ErrorToRetry(TypeError)], - ) - - timing.log('widget_index_lookup_done') - - self._chain.verbose = True - result = self._chain.run() + example = { + "question": query, + "stop": "User", + } + result = self._chain.run(example) return result + + async def _arun(self, query: str) -> str: + raise NotImplementedError(f"{self.__class__.__name__} does not support async") \ No newline at end of file From 2f9f7923ca9e1aa25694e27024f87c3603de560c Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 10:44:13 -0700 Subject: [PATCH 17/30] fix: remove example in prompt --- tools/index_generate_code.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/index_generate_code.py b/tools/index_generate_code.py index 0514116..ab2631a 100644 --- a/tools/index_generate_code.py +++ b/tools/index_generate_code.py @@ -16,9 +16,6 @@ - Assume there is an ethers.js provider and signer available and can be provided to the function - Your final output should be a JSON object with a code field which contains formatted JS code with comments -# USER REQUEST EXAMPLE -write code to send 2 eth to 0x123 - User: {question} Assistant:''' From be0d525df1d4756968497211153c2f4bb3dbca43 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 10:57:04 -0700 Subject: [PATCH 18/30] fix: prompt --- tools/index_generate_code.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/index_generate_code.py b/tools/index_generate_code.py index ab2631a..17289dc 100644 --- a/tools/index_generate_code.py +++ b/tools/index_generate_code.py @@ -10,12 +10,13 @@ TEMPLATE = '''You are an expert Web3 developer well versed in using JS to interact with the ecosystem, you will help the user perform actions based on their request by generating functional JS code # INSTRUCTIONS -- Assume user wallet already connected to browser so don't ask for private key, Infura project ID, or any credentials -- Print out transaction hash +- Assume user wallet already connected to browser so never ask for a private key, Infura project ID, or any credentials +- Print out transaction hash if applicable - Always use ethers.js -- Assume there is an ethers.js provider and signer available and can be provided to the function +- Assume there is an ethers.js provider and signer available and can be provided to the function or code - Your final output should be a JSON object with a code field which contains formatted JS code with comments +--- User: {question} Assistant:''' From 543e8c6a3d7aa7458d79f6b92319aea8c4c7cef3 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 10:57:13 -0700 Subject: [PATCH 19/30] fix: add 'display' --- knowledge_base/widgets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 622e7a8..a1724da 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -855,7 +855,7 @@ - vault type: object return_value_description: '' -- _name_: generate_code +- _name_: display_generate_code description: generate code for the user; this widget should always be chosen for anything relating to code parameters: properties: From ca6bb86de2cfaf6119e3f82633242f28cc7e67ab Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 11:01:10 -0700 Subject: [PATCH 20/30] chore: name change --- tools/index_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/index_widget.py b/tools/index_widget.py index 37a1ece..999985e 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -398,7 +398,7 @@ def fn(token_handler): tool = dict( type="tools.index_generate_code.IndexGenerateCodeTool", _streaming=True, - name="GenerateCode", + name="IndexGenerateCodeTool", content_description="", # not used ) tool = streaming.get_streaming_tools([tool], token_handler)[0] From 2cce3af42ba91411ae30ab19161581b8c0506361 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 11:01:50 -0700 Subject: [PATCH 21/30] chore: order --- tools/index_widget.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/index_widget.py b/tools/index_widget.py index 999985e..505827f 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -396,10 +396,9 @@ def fn(token_handler): def generate_code(query: str) -> Callable: def fn(token_handler): tool = dict( + name="IndexGenerateCodeTool", type="tools.index_generate_code.IndexGenerateCodeTool", _streaming=True, - name="IndexGenerateCodeTool", - content_description="", # not used ) tool = streaming.get_streaming_tools([tool], token_handler)[0] tool._run(query) From 53874409b133b0db6d5f52a4078b65b2bfc5722c Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 11:47:26 -0700 Subject: [PATCH 22/30] fix: rename --- chat/display_widgets.py | 3 --- knowledge_base/widgets.yaml | 2 +- tools/__init__.py | 2 +- tools/{index_generate_code.py => generate_js_code.py} | 2 +- tools/index_widget.py | 6 +++--- 5 files changed, 6 insertions(+), 9 deletions(-) rename tools/{index_generate_code.py => generate_js_code.py} (98%) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index f0210a4..2d7fb05 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -201,9 +201,6 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'withdraw-vault': items = params.split(",") lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") - elif command == 'generate-code': - items = params.split(",") - lines.append(f"supply the user with valid javascript code to run and perform their desired action.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index a1724da..05f2f02 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -855,7 +855,7 @@ - vault type: object return_value_description: '' -- _name_: display_generate_code +- _name_: generate_js_code description: generate code for the user; this widget should always be chosen for anything relating to code parameters: properties: diff --git a/tools/__init__.py b/tools/__init__.py index 819929b..94b9191 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -7,4 +7,4 @@ from . import index_api_tool from . import app_usage_guide from . import index_link_suggestion -from . import index_generate_code \ No newline at end of file +from . import generate_js_code \ No newline at end of file diff --git a/tools/index_generate_code.py b/tools/generate_js_code.py similarity index 98% rename from tools/index_generate_code.py rename to tools/generate_js_code.py index 17289dc..66ff415 100644 --- a/tools/index_generate_code.py +++ b/tools/generate_js_code.py @@ -22,7 +22,7 @@ @registry.register_class -class IndexGenerateCodeTool(BaseTool): +class GenerateJSCodeTool(BaseTool): """Tool for generating code to perform a user request.""" _chain: LLMChain diff --git a/tools/index_widget.py b/tools/index_widget.py index 505827f..ad0e5de 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -393,11 +393,11 @@ def fn(token_handler): return fn @error_wrap -def generate_code(query: str) -> Callable: +def generate_js_code(query: str) -> Callable: def fn(token_handler): tool = dict( - name="IndexGenerateCodeTool", - type="tools.index_generate_code.IndexGenerateCodeTool", + name="GenerateJSCodeTool", + type="tools.generate_js_code.GenerateJSCodeTool", _streaming=True, ) tool = streaming.get_streaming_tools([tool], token_handler)[0] From 92602ae9a37a3bd60c5e952d1ef7506afdcc5c7d Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 11:50:00 -0700 Subject: [PATCH 23/30] fix: ref --- tools/index_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/index_widget.py b/tools/index_widget.py index ad0e5de..05fb049 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -250,8 +250,8 @@ def replace_match(m: re.Match) -> Union[str, Generator, Callable]: # return fetch_scraped_sites(*params) elif command == 'fetch-link-suggestion': return fetch_link_suggestion(*params) - elif command == 'generate-code': - return generate_code(*params) + elif command == 'generate-js-code': + return generate_js_code(*params) elif command == aave.AaveSupplyContractWorkflow.WORKFLOW_TYPE: return str(exec_aave_operation(*params, operation='supply')) elif command == aave.AaveBorrowContractWorkflow.WORKFLOW_TYPE: From 3e727468e5245799300494c9ac983f8795757bef Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 11:55:47 -0700 Subject: [PATCH 24/30] feat: template instruction update --- tools/generate_js_code.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/generate_js_code.py b/tools/generate_js_code.py index 66ff415..dd355b6 100644 --- a/tools/generate_js_code.py +++ b/tools/generate_js_code.py @@ -14,6 +14,7 @@ - Print out transaction hash if applicable - Always use ethers.js - Assume there is an ethers.js provider and signer available and can be provided to the function or code +- The code should return a function or a promise that can be called to perform the action - Your final output should be a JSON object with a code field which contains formatted JS code with comments --- From 0418c3390d3cce512c0fe1edbcb422b7d510ca56 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Tue, 26 Sep 2023 12:01:26 -0700 Subject: [PATCH 25/30] fix: update ref --- eval/eval_widgets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eval/eval_widgets.txt b/eval/eval_widgets.txt index 4181398..f6a545e 100644 --- a/eval/eval_widgets.txt +++ b/eval/eval_widgets.txt @@ -34,4 +34,4 @@ display_uniswap fetch_nfts_owned_by_address_or_domain fetch_nfts_owned_by_user fetch_link_suggestion -generate_code \ No newline at end of file +generate_js_code \ No newline at end of file From 03dd107d272578382d7a2e161b7c9d5636e0e633 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 27 Sep 2023 12:45:16 -0700 Subject: [PATCH 26/30] fix: better output description --- tools/generate_js_code.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/generate_js_code.py b/tools/generate_js_code.py index dd355b6..aba78d3 100644 --- a/tools/generate_js_code.py +++ b/tools/generate_js_code.py @@ -15,7 +15,7 @@ - Always use ethers.js - Assume there is an ethers.js provider and signer available and can be provided to the function or code - The code should return a function or a promise that can be called to perform the action -- Your final output should be a JSON object with a code field which contains formatted JS code with comments +- Your final output should be a JSON object with a code field, which contains a formatted JS code function with comments, which can be run on a frontend; don't include anything else for now (ie: messages that precede the code, etc.) --- User: {question} @@ -46,7 +46,6 @@ def __init__( description=BASE_TOOL_DESCRIPTION_TEMPLATE.format( tool_description="generate code based on the user query", input_description="a standalone query where user wants to generate code to perform an action", - output_description="a message answer, along with the generated code with helpful comments", ) super().__init__( From 930e928f2b9df880b4077a0d8d765ca9f3dbb39c Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 27 Sep 2023 13:20:49 -0700 Subject: [PATCH 27/30] fix: need output descript --- tools/generate_js_code.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/generate_js_code.py b/tools/generate_js_code.py index aba78d3..db74886 100644 --- a/tools/generate_js_code.py +++ b/tools/generate_js_code.py @@ -43,10 +43,11 @@ def __init__( ) new_token_handler = kwargs.get('new_token_handler') chain = streaming.get_streaming_chain(prompt, new_token_handler) - description=BASE_TOOL_DESCRIPTION_TEMPLATE.format( - tool_description="generate code based on the user query", - input_description="a standalone query where user wants to generate code to perform an action", - ) + description = BASE_TOOL_DESCRIPTION_TEMPLATE.format( + tool_description="generate code based on the user query", + input_description="a standalone query where user wants to generate code to perform an action", + output_description="a JSON object with a code field, which contains a formatted JS code function with comments, which can be run on a frontend; don't include anything else for now (ie: messages that precede the code, etc.)" + ) super().__init__( *args, @@ -61,8 +62,10 @@ def _run(self, query: str) -> str: "stop": "User", } result = self._chain.run(example) + print('result in generate_js_code', result) return result async def _arun(self, query: str) -> str: - raise NotImplementedError(f"{self.__class__.__name__} does not support async") \ No newline at end of file + raise NotImplementedError( + f"{self.__class__.__name__} does not support async") From 68f27974c32fe41ad3596b26e12a96e82868b173 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 27 Sep 2023 13:51:32 -0700 Subject: [PATCH 28/30] feat: trying to implement a CodeContainer for the generate_js_code handler func --- knowledge_base/widgets.yaml | 38 +++++++-------- tools/index_widget.py | 96 ++++++++++++++++++++++++++----------- 2 files changed, 88 insertions(+), 46 deletions(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 05f2f02..42b88b6 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -1,22 +1,22 @@ -- _name_: display_transfer - description: Transfer a token from a user's wallet to another address - parameters: - properties: - address: - description: Transfer recipient address. - type: string - amount: - description: Quantity to transfer. - type: string - token: - description: Symbol of the token being transferred. - type: string - required: - - token - - amount - - address - type: object - return_value_description: '' +# - _name_: display_transfer +# description: Transfer a token from a user's wallet to another address +# parameters: +# properties: +# address: +# description: Transfer recipient address. +# type: string +# amount: +# description: Quantity to transfer. +# type: string +# token: +# description: Symbol of the token being transferred. +# type: string +# required: +# - token +# - amount +# - address +# type: object +# return_value_description: '' - _name_: fetch_nft_buy_asset description: Buy an NFT asset of a collection on the OpenSea marketplace, given its network, address, and token ID. Don't use this if we don't have the collection diff --git a/tools/index_widget.py b/tools/index_widget.py index 05fb049..c50c8db 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -92,7 +92,8 @@ def injection_handler(token): # we have found the response_prefix, trim everything before that timing.log('first_widget_response_token') response_state = 1 - response_buffer = response_buffer[response_buffer.index(response_prefix) + len(response_prefix):] + response_buffer = response_buffer[response_buffer.index( + response_prefix) + len(response_prefix):] if response_state == 1: # we are going to output the response incrementally, evaluating any fetch commands while WIDGET_START in response_buffer and self._evaluate_widgets: @@ -102,14 +103,17 @@ def injection_handler(token): if isinstance(response_buffer, Callable): # handle delegated streaming def handler(token): nonlocal new_token_handler - timing.log('first_visible_widget_response_token') + timing.log( + 'first_visible_widget_response_token') return new_token_handler(token) response_buffer(handler) response_buffer = "" return - elif isinstance(response_buffer, Generator): # handle stream of widgets + # handle stream of widgets + elif isinstance(response_buffer, Generator): for item in response_buffer: - timing.log('first_visible_widget_response_token') + timing.log( + 'first_visible_widget_response_token') new_token_handler(str(item) + "\n") response_buffer = "" return @@ -118,7 +122,8 @@ def handler(token): # NB: for better frontend parsing of nested widgets, we need an invariant that # there are no two independent widgets on the same line, otherwise we can't # detect the closing tag properly when there is nesting. - response_buffer = response_buffer.replace(WIDGET_END, WIDGET_END + '\n') + response_buffer = response_buffer.replace( + WIDGET_END, WIDGET_END + '\n') break else: # keep waiting @@ -139,7 +144,8 @@ def handler(token): # we have found a line-break in the response, switch to the terminal state to mask subsequent output response_state = 2 - chain = streaming.get_streaming_chain(prompt, injection_handler, model_name=model_name) + chain = streaming.get_streaming_chain( + prompt, injection_handler, model_name=model_name) super().__init__( *args, _chain=chain, @@ -165,7 +171,7 @@ def _run(self, query: str) -> str: def iterative_evaluate(phrase: str) -> Union[str, Generator, Callable]: while True: # before we had streaming, we could use this - #eval_phrase = RE_COMMAND.sub(replace_match, phrase) + # eval_phrase = RE_COMMAND.sub(replace_match, phrase) # now, iterate manually to find any streamable components eval_phrase = "" last_matched_char = 0 @@ -249,9 +255,9 @@ def replace_match(m: re.Match) -> Union[str, Generator, Callable]: # elif command == 'fetch-scraped-sites': # return fetch_scraped_sites(*params) elif command == 'fetch-link-suggestion': - return fetch_link_suggestion(*params) + return fetch_link_suggestion(*params) elif command == 'generate-js-code': - return generate_js_code(*params) + return generate_js_code(*params) elif command == aave.AaveSupplyContractWorkflow.WORKFLOW_TYPE: return str(exec_aave_operation(*params, operation='supply')) elif command == aave.AaveBorrowContractWorkflow.WORKFLOW_TYPE: @@ -279,6 +285,7 @@ def replace_match(m: re.Match) -> Union[str, Generator, Callable]: # assert 0, 'unrecognized command: %s' % m.group(0) return m.group(0) + @error_wrap def fetch_price(basetoken: str, quotetoken: str = "usd") -> str: # TODO @@ -304,7 +311,8 @@ def fetch_price(basetoken: str, quotetoken: str = "usd") -> str: else: return f"Quote currency {quotetoken} not supported" - coingecko_api_url = coingecko_api_url_prefix + f"?ids={basetoken_id}&vs_currencies={quotetoken_id}" + coingecko_api_url = coingecko_api_url_prefix + \ + f"?ids={basetoken_id}&vs_currencies={quotetoken_id}" response = requests.get(coingecko_api_url) response.raise_for_status() return f"The price of {basetoken_name} is {list(list(response.json().values())[0].values())[0]} {quotetoken}" @@ -313,7 +321,8 @@ def fetch_price(basetoken: str, quotetoken: str = "usd") -> str: @error_wrap def fetch_balance(token: str, wallet_address: str) -> str: if not wallet_address or wallet_address == 'None': - raise FetchError(f"Please specify the wallet address to check the token balance of.") + raise FetchError( + f"Please specify the wallet address to check the token balance of.") web3 = context.get_web3_provider() chain_id = context.get_wallet_chain_id() balance = get_token_balance(web3, chain_id, token, wallet_address) @@ -367,7 +376,7 @@ def fn(token_handler): # _streaming=True, # name="ScrapedSitesIndexAnswer", # content_description="", # not used -# index=scraped_sites_index, +# index=scraped_sites_index, # top_k=3, # source_key="url", # ) @@ -375,6 +384,7 @@ def fn(token_handler): # tool._run(query) # return fn + @error_wrap def fetch_link_suggestion(query: str) -> Callable: def fn(token_handler): @@ -384,7 +394,7 @@ def fn(token_handler): _streaming=True, name="LinkSuggestionIndexAnswer", content_description="", # not used - index=dapps_index, + index=dapps_index, top_k=3, source_key="url", ) @@ -392,6 +402,7 @@ def fn(token_handler): tool._run(query) return fn + @error_wrap def generate_js_code(query: str) -> Callable: def fn(token_handler): @@ -401,8 +412,8 @@ def fn(token_handler): _streaming=True, ) tool = streaming.get_streaming_tools([tool], token_handler)[0] - tool._run(query) - return fn + return tool._run(query) + return str(CodeContainer(code=fn())) class ListContainer(ContainerMixin, list): @@ -475,6 +486,27 @@ def container_params(self) -> Dict: ) +@dataclass +class CodeContainer(ContainerMixin): + code: str + message_prefix_str: str = "" + message_suffix_str: str = "" + + def message_prefix(self) -> str: + return self.message_prefix_str + + def message_suffix(self) -> str: + return self.message_suffix_str + + def container_name(self) -> str: + return 'display-code-container' + + def container_params(self) -> Dict: + return dict( + code=self.code, + ) + + @error_wrap def fetch_nft_search(search_str: str) -> Generator: yield StreamingListContainer(operation="create", prefix="Searching", is_thinking=True) @@ -565,10 +597,12 @@ def fetch_nfts_owned_by_user(network: str = None) -> str: parsed_network = network return str(center.fetch_nfts_owned_by_address_or_domain(parsed_network, wallet_address)) + @error_wrap def fetch_nft_buy(network: str, address: str, token_id: str) -> str: wallet_address = context.get_wallet_address() - nft_fulfillment_container = center.fetch_nft_buy(network, wallet_address, address, token_id) + nft_fulfillment_container = center.fetch_nft_buy( + network, wallet_address, address, token_id) return str(nft_fulfillment_container) @@ -584,10 +618,12 @@ def fetch_yields(token, network, count) -> str: ] if network == '*': - headers = [TableHeader(field_name="network", display_name="Network")] + headers + headers = [TableHeader(field_name="network", + display_name="Network")] + headers if token == '*': - headers = [TableHeader(field_name="token", display_name="Token")] + headers + headers = [TableHeader( + field_name="token", display_name="Token")] + headers table_container = TableContainer(headers=headers, rows=yields) return str(table_container) @@ -647,10 +683,11 @@ def container_name(self) -> str: def container_params(self) -> Dict: return dataclass_to_container_params(self) + @error_wrap @ensure_wallet_connected -def set_ens_text(domain: str, key: str, value: str) ->TxPayloadForSending: - wallet_chain_id = 1 # TODO: get from context +def set_ens_text(domain: str, key: str, value: str) -> TxPayloadForSending: + wallet_chain_id = 1 # TODO: get from context wallet_address = context.get_wallet_address() user_chat_message_id = context.get_user_chat_message_id() @@ -660,13 +697,15 @@ def set_ens_text(domain: str, key: str, value: str) ->TxPayloadForSending: 'value': value, } - result = ens.ENSSetTextWorkflow(wallet_chain_id, wallet_address, user_chat_message_id, params).run() + result = ens.ENSSetTextWorkflow( + wallet_chain_id, wallet_address, user_chat_message_id, params).run() return TxPayloadForSending.from_workflow_result(result) + @error_wrap @ensure_wallet_connected -def set_ens_primary_name(domain: str) ->TxPayloadForSending: - wallet_chain_id = 1 # TODO: get from context +def set_ens_primary_name(domain: str) -> TxPayloadForSending: + wallet_chain_id = 1 # TODO: get from context wallet_address = context.get_wallet_address() user_chat_message_id = context.get_user_chat_message_id() @@ -674,13 +713,15 @@ def set_ens_primary_name(domain: str) ->TxPayloadForSending: 'domain': domain, } - result = ens.ENSSetPrimaryNameWorkflow(wallet_chain_id, wallet_address, user_chat_message_id, params).run() + result = ens.ENSSetPrimaryNameWorkflow( + wallet_chain_id, wallet_address, user_chat_message_id, params).run() return TxPayloadForSending.from_workflow_result(result) + @error_wrap @ensure_wallet_connected -def set_ens_avatar_nft(domain: str, nftContractAddress: str, nftId: str, collectionName: str) ->TxPayloadForSending: - wallet_chain_id = 1 # TODO: get from context +def set_ens_avatar_nft(domain: str, nftContractAddress: str, nftId: str, collectionName: str) -> TxPayloadForSending: + wallet_chain_id = 1 # TODO: get from context wallet_address = context.get_wallet_address() user_chat_message_id = context.get_user_chat_message_id() @@ -691,5 +732,6 @@ def set_ens_avatar_nft(domain: str, nftContractAddress: str, nftId: str, collect 'collectionName': collectionName } - result = ens.ENSSetAvatarNFTWorkflow(wallet_chain_id, wallet_address, user_chat_message_id, params).run() + result = ens.ENSSetAvatarNFTWorkflow( + wallet_chain_id, wallet_address, user_chat_message_id, params).run() return TxPayloadForSending.from_workflow_result(result) From 379bebbdfdc03b2aa0e02253d47dbb5942168c89 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Wed, 27 Sep 2023 14:51:54 -0700 Subject: [PATCH 29/30] chore: print --- tools/index_widget.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/index_widget.py b/tools/index_widget.py index c50c8db..faaed3b 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -412,8 +412,12 @@ def fn(token_handler): _streaming=True, ) tool = streaming.get_streaming_tools([tool], token_handler)[0] + print('running tool') return tool._run(query) - return str(CodeContainer(code=fn())) + code = fn() + print('code', code) + print('testing') + return str(CodeContainer(code=code)) class ListContainer(ContainerMixin, list): From 6dab4aca1fa26bfedb2514faa377a9b1f6c1cc32 Mon Sep 17 00:00:00 2001 From: marcomariscal Date: Thu, 28 Sep 2023 10:50:34 -0700 Subject: [PATCH 30/30] feat: code container --- knowledge_base/widgets.yaml | 10 +++++----- tools/generate_js_code.py | 2 +- tools/index_widget.py | 19 ++++++++----------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 42b88b6..4daa64d 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -856,13 +856,13 @@ type: object return_value_description: '' - _name_: generate_js_code - description: generate code for the user; this widget should always be chosen for anything relating to code + description: generate code for the user based on the query; this widget should always be chosen for anything relating to code within a query parameters: properties: - user_wallet_address: - description: default to using the connected address + query: + description: a standalone question representing the user's intent to perform an action via code type: string required: - - user_wallet_address + - query type: object - return_value_description: 'message with formatted javascript code in a property called code in JSON format' + return_value_description: formatted javascript code diff --git a/tools/generate_js_code.py b/tools/generate_js_code.py index db74886..a5ce511 100644 --- a/tools/generate_js_code.py +++ b/tools/generate_js_code.py @@ -15,7 +15,7 @@ - Always use ethers.js - Assume there is an ethers.js provider and signer available and can be provided to the function or code - The code should return a function or a promise that can be called to perform the action -- Your final output should be a JSON object with a code field, which contains a formatted JS code function with comments, which can be run on a frontend; don't include anything else for now (ie: messages that precede the code, etc.) +- Your final output should be a formatted JS code function with comments, which can be run on a frontend; don't include anything else for now (ie: messages that precede the code, etc.) --- User: {question} diff --git a/tools/index_widget.py b/tools/index_widget.py index faaed3b..5881cfb 100644 --- a/tools/index_widget.py +++ b/tools/index_widget.py @@ -405,18 +405,15 @@ def fn(token_handler): @error_wrap def generate_js_code(query: str) -> Callable: - def fn(token_handler): - tool = dict( - name="GenerateJSCodeTool", - type="tools.generate_js_code.GenerateJSCodeTool", - _streaming=True, - ) - tool = streaming.get_streaming_tools([tool], token_handler)[0] - print('running tool') - return tool._run(query) - code = fn() + tool = dict( + name="GenerateJSCodeTool", + type="tools.generate_js_code.GenerateJSCodeTool", + _streaming=True, + ) + tool = streaming.get_streaming_tools([tool], None)[0] + print('running tool') + code =tool._run(query) print('code', code) - print('testing') return str(CodeContainer(code=code))