├── .dockerignore ├── .env.template ├── .gitignore ├── .pylintrc ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── data ├── examples │ ├── 1 │ ├── 2 │ ├── 3 │ └── 4 └── prompts │ ├── clarify_sys │ ├── clarify_user │ ├── jvm_spec │ ├── plan_eval_sys │ ├── plan_eval_user │ ├── planner_sys │ ├── reviewer_eval_syntax │ ├── reviewer_index_key │ ├── reviewer_simulation_output │ ├── reviewer_simulation_regenerate │ ├── reviewer_simulation_sys │ ├── reviewer_simulation_user │ ├── reviewer_syntax_sys │ ├── reviewer_syntax_user │ ├── reviewer_sys │ ├── reviser_sys │ ├── reviser_user │ ├── text_completion_sys │ ├── text_completion_user │ ├── translator_sys │ ├── translator_user │ └── translator_user_w_user_result ├── evaluator ├── customer_evaluator.py └── evaluate_translator.py ├── example.ipynb ├── experiments ├── autogen_jarvis.py ├── react.py └── reflect.py ├── jarvis ├── README.md ├── __init__.py ├── __main__.py ├── agent │ ├── __init__.py │ ├── jarvis_agent.py │ └── skill.py ├── server │ ├── __init__.py │ ├── __main__.py │ ├── jarvis.proto │ ├── jarvis_client.py │ ├── jarvis_pb2.py │ ├── jarvis_pb2_grpc.py │ └── jarvis_server.py ├── setup.py ├── smartgpt │ ├── __init__.py │ ├── actions.py │ ├── clarifier.py │ ├── compiler.py │ ├── fewshot.py │ ├── gpt.py │ ├── initializer.py │ ├── instruction.py │ ├── jvm.py │ ├── planner.py │ ├── preprompts.py │ ├── reviewer.py │ ├── spinner.py │ ├── translator.py │ └── utils.py └── utils │ ├── __init__.py │ └── tracer.py ├── run_goal.sh ├── run_skill_chain.py └── tests ├── __init__.py ├── test_actions.py ├── test_instruction.py ├── test_planner.py └── test_utils.py /.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | .env 3 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # cp .env.example .env 2 | # Edit your .env file with your own values 3 | # Don't commit your .env file to git/push to GitHub! 4 | # Don't modify/delete .env.example unless adding extensions to the project 5 | # which require new variable to be added to the .env file 6 | 7 | PYTHONPATH= 8 | 9 | # Jarvis environment variables 10 | 11 | OPENAI_API_TYPE="openai" 12 | OPENAI_API_BASE= 13 | OPENAI_API_VERSION= 14 | OPENAI_API_KEY= 15 | OPENAI_TEMPERATURE=0.2 16 | 17 | GOOGLE_API_KEY= 18 | GOOGLE_SEARCH_ENGINE_ID= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | /__MACOSX 4 | __pycache__ 5 | .env 6 | /workspace 7 | /smartgpt.egg-info 8 | /dist 9 | /build 10 | /output 11 | .cache 12 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | max-line-length=120 3 | 4 | [MESSAGES CONTROL] 5 | disable=C0111,E0633, logging-fstring-interpolation, unspecified-encoding, broad-exception-caught, invalid-name, global-statement -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Run Server", 6 | "type": "python", 7 | "request": "launch", 8 | "module": "jarvis.server", 9 | "args": [], 10 | "console": "integratedTerminal", 11 | "justMyCode": true 12 | }, 13 | { 14 | "name": "Python: Run #Task instructions", 15 | "type": "python", 16 | "request": "launch", 17 | "module": "jarvis", 18 | "args": ["--yaml", "1.yaml"], 19 | "console": "integratedTerminal", 20 | "justMyCode": true 21 | }, 22 | { 23 | "name": "Python: Generate plan interactively", 24 | "type": "python", 25 | "request": "launch", 26 | "module": "jarvis", 27 | "args": ["--replan"], 28 | "console": "integratedTerminal", 29 | "justMyCode": true 30 | }, 31 | { 32 | "name": "Python: Generate plan with Goalfile", 33 | "type": "python", 34 | "request": "launch", 35 | "module": "jarvis", 36 | "args": ["--replan", "--goalfile=goal"], 37 | "console": "integratedTerminal", 38 | "justMyCode": true 39 | }, 40 | { 41 | "name": "Python: Compile all task in plan", 42 | "type": "python", 43 | "request": "launch", 44 | "module": "jarvis", 45 | "args": [], 46 | "console": "integratedTerminal", 47 | "justMyCode": true 48 | }, 49 | { 50 | "name": "Python: Re-compile a given task", 51 | "type": "python", 52 | "request": "launch", 53 | "module": "jarvis", 54 | "args": ["--compile", "2"], 55 | "console": "integratedTerminal", 56 | "justMyCode": true 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintArgs": ["--rcfile=${workspaceFolder}/.pylintrc"], 3 | "python.linting.pylintEnabled": true, 4 | "python.linting.enabled": true, 5 | "prettier.tabWidth": 4, 6 | "prettier.printWidth": 200, 7 | "python.formatting.provider": "autopep8", 8 | "python.analysis.typeCheckingMode": "basic", 9 | "python.analysis.extraPaths": [ 10 | "./jarvis" 11 | ], 12 | "[python]": { 13 | "editor.defaultFormatter": "ms-python.python" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.13 2 | 3 | WORKDIR /app 4 | 5 | RUN pip install --upgrade pip 6 | RUN pip install pipenv 7 | 8 | COPY Pipfile Pipfile.lock ./ 9 | RUN pipenv install --deploy --ignore-pipfile --verbose 10 | 11 | # Install browsers 12 | RUN apt-get update && apt-get install -y \ 13 | chromium-driver firefox-esr \ 14 | ca-certificates libnss3 \ 15 | sqlite3 libsqlite3-dev 16 | 17 | COPY data/ ./data/ 18 | COPY jarvis/ ./jarvis/ 19 | 20 | RUN mkdir /app/workspace 21 | 22 | EXPOSE 51155 23 | 24 | # ENV MY_ENV_VAR=value 25 | 26 | # Run jarvis-server 27 | CMD ["pipenv", "run", "python", "-m", "jarvis.server"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rok Strniša 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | openai = "*" 8 | playsound = "*" 9 | python-dotenv = "*" 10 | googlesearch-python = "*" 11 | selenium = "==4.9.1" 12 | tiktoken = "*" 13 | webdriver-manager = "*" 14 | peewee = "*" 15 | pyyaml = "*" 16 | argparse = "==1.4.0" 17 | pre-commit = ">=3.2.0" 18 | llama-cpp-python = ">=0.1.42" 19 | pydantic = "*" 20 | grpcio = "*" 21 | grpcio-tools = "*" 22 | langchain = "*" 23 | chroma-hnswlib = "==0.7.2" 24 | chromadb = "==0.4.5" 25 | 26 | [dev-packages] 27 | pylint = "*" 28 | black = "*" 29 | 30 | [requires] 31 | python_version = "3.10" 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jarvis: AI-Powered Virtual Machine 2 | 3 | Welcome to Jarvis, a cutting-edge virtual machine designed specifically to facilitate AI tasks. This README offers a comprehensive guide to effectively set up and utilize Jarvis for optimal results. 4 | 5 | ## Demo 6 | 7 | Jarvis collaborates with Microsoft's AutoGen to do tweet analysis. 8 | 9 | https://github.com/ngaut/jarvis/assets/10701973/2fe3fc02-93e0-4a62-8ffe-0de96b68d401 10 | 11 | 12 | ## Prerequisites 13 | 14 | 1. Obtain the necessary API keys to interface with third-party services: 15 | - [OPENAI_API_KEY](https://platform.openai.com/account/api-keys) 16 | - Both `GOOGLE_API_KEY` and `GOOGLE_SEARCH_ENGINE_ID` for integrating Google Search Engine. 17 | 18 | ## Installation and Setup 19 | 20 | 1. Clone this repository: 21 | 22 | ``` 23 | git clone https://github.com/ngaut/jarvis.git && cd jarvis 24 | ``` 25 | 26 | 2. Set up the environment variables: 27 | 28 | - Rename `.env.template` to `.env`, and input the necessary API keys mentioned in the Prerequisites section. 29 | 30 | 3. Build a Local Docker Image 31 | 32 | ``` 33 | docker build -t jarvis-server:latest . 34 | ``` 35 | 36 | 4. Running Jarvis Server 37 | 38 | The Jarvis server operates on port `51155` by default, offering services via the gRPC protocol. 39 | 40 | To run the Jarvis server with Docker: 41 | 42 | ``` 43 | docker run --rm -p 51155:51155 \ 44 | -v $(pwd)/workspace:/app/workspace \ 45 | -v $(pwd)/.env:/app/.env \ 46 | jarvis-server:latest 47 | ``` 48 | 49 | Note: Ensure you've configured the `.env` file in the current directory before proceeding. 50 | 51 | ## Usage 52 | 53 | For guidance, you can refer to the code provided in this [demo](example.ipynb) 54 | 55 | ### Develop a Skill: 56 | 57 | Develop a skill that generates summaries of top stories from Hacknews. 58 | 59 | ```python 60 | stub.Execute( 61 | jarvis_pb2.ExecuteRequest( 62 | task=( 63 | "Collect the top three articles featured on Hacker News (https://news.ycombinator.com/), " 64 | "and produce a single professional reading summary that encompasses the content of all three articles, formatted in a user-friendly manner." 65 | ) 66 | ) 67 | ) 68 | ``` 69 | 70 | Task output example: 71 | 72 | ``` 73 | executor_id: "ea8fcfdf59c011002875a88fcdac5e97" 74 | task_id: 1 75 | task: Collect the top three articles featured on Hacker News (https://news.ycombinator.com/), and produce a single professional reading summary that encompasses the content of all three articles, formatted in a user-friendly manner. 76 | result: "The University of Turku in Finland is developing an artificial language corpus proficient in all European languages ..." 77 | ``` 78 | 79 | ### Save a Skill: 80 | 81 | A step-by-step guide to save a developed skill for subsequent use. 82 | 83 | ```python 84 | stub.SaveSkill( 85 | jarvis_pb2.SaveSkillRequest( 86 | executor_id="ea8fcfdf59c011002875a88fcdac5e97", 87 | skill_name="HackerNews top three articles summary", 88 | ) 89 | ) 90 | ``` 91 | 92 | Task output example: 93 | 94 | ``` 95 | executor_id: "ea8fcfdf59c011002875a88fcdac5e97" 96 | result: "skill is saved as HackerNews top three articles summary" 97 | ``` 98 | 99 | 100 | ### Reuse Skills: 101 | 102 | Recall and utilize previously saved skills for the same or related tasks. 103 | 104 | ```python 105 | python run_skill_chain.py --workspace=workspace --skill_dir=skill_library --execution_dir=summary_hn_news --skills="HackerNews top three articles summary" 106 | ``` 107 | 108 | Task output example: 109 | 110 | ``` 111 | executing skill: HackerNews top three articles summary 112 | -------------------------------------------------- 113 | Skill Execution Summary 114 | -------------------------------------------------- 115 | 116 | Skill Result: The article discusses a 3 state, 3 symbol Turing Machine called 'Bigfoot' that cannot be proven to halt or not without solving a Collatz-like problem ... 117 | Skill Error: None 118 | 119 | ================================================== 120 | Detailed Task Infos 121 | ================================================== 122 | 123 | Subtask: Collect the top three articles featured on Hacker News (https://news.ycombinator.com/), and produce a single professional reading summary that encompasses the content of all three articles, formatted in a user-friendly manner. 124 | Result: ... 125 | Error: None 126 | 127 | -------------------------------------------------- 128 | 129 | End of Execution Summary 130 | -------------------------------------------------- 131 | ``` 132 | ## Special Thanks to RoboGPT 133 | 134 | We want to express our deepest gratitude to [RoboGPT](https://github.com/rokstrnisa/RoboGPT). Their pioneering work and groundbreaking contributions have been a source of inspiration in the development of Jarvis. 135 | -------------------------------------------------------------------------------- /data/examples/1: -------------------------------------------------------------------------------- 1 | ### An Output Example 2 | ```yaml 3 | task: "Get current weather data for San Francisco and provide suggestions based on temperature, save the results to file" 4 | objective: ... 5 | thoughts: # AI-generated thoughts content, should be plain text without newlines, wrapped in quotes 6 | hints_from_user: # A list of hints from the user, each item must be plain text and wrapped in quotes 7 | start_seq: 1 # user-specified start_seq 8 | instructions: 9 | - seq: 1 10 | type: WebSearch 11 | inside_loop: false 12 | objective: "Find URLs related to current weather in San Francisco" 13 | rule_num: 2 14 | args: 15 | query: "temperature in San Francisco" 16 | save_to: "search_result_urls.seq1.list" 17 | - seq: 2 18 | type: FetchWebContent 19 | inside_loop: false 20 | rule_num: 2 21 | objective: "Fetch the content from the first URL from the search results" 22 | args: 23 | url: "jvm.eval(jvm.get('search_result_urls.seq1.list')[0])" # make sure the reference key exists. 24 | save_to: "fetched_content.seq2.str" # without as not inside a loop 25 | - seq: 3 26 | type: TextCompletion 27 | inside_loop: false 28 | objective: "Get the current temperature in San Francisco from the fetched content" 29 | rule_num: 3 30 | args: 31 | request: "Get the current temperature and url in San Francisco" 32 | output_format: 33 | kvs: 34 | - key: "temperature.seq3.int" # without as not inside a loop 35 | value: "" 36 | - key: "source_url.seq3.str" 37 | value: "" 38 | content: "jvm.eval(jvm.get('fetched_content.seq2.str'))" 39 | - seq: 4 40 | type: If 41 | inside_loop: false 42 | objective: Evaluate condition to decide if we recommend outdoor or indoor activities 43 | rule_num: 5 44 | args: 45 | condition: "20 < jvm.eval(jvm.get('temperature.seq3.int')) < 30" 46 | then: 47 | - seq: 5 48 | type: TextCompletion 49 | inside_loop: false 50 | objective: "Generate outdoor activities suggestions" 51 | rule_num: 3 52 | args: 53 | request: "What outdoor activities should we recommend to the users? Please generate a weather notes" 54 | output_format: 55 | kvs: 56 | - key: "weather_notes.seq5.str" 57 | value: "" 58 | content: "Today's temperature in San Francisco is jvm.eval(jvm.get('temperature.seq3.int'))" 59 | else: 60 | - seq: 6 61 | type: TextCompletion 62 | inside_loop: false 63 | objective: "Generate indoor activities suggestions" 64 | rule_num: 3 65 | args: 66 | request: "What indoor activities should we recommend to the users? Please generate a weather notes" 67 | output_format: 68 | kvs: 69 | - key: "weather_notes.seq6.str" 70 | value: "" 71 | content: "Today's temperature in San Francisco is jvm.eval(jvm.get('temperature.seq3.int'))" 72 | - seq: 7 73 | type: TextCompletion 74 | inside_loop: false 75 | objective: "Generate a complete weather report for San Francisco using the gathered information" 76 | rule_num: 3 77 | args: 78 | request: "Please generate current weather report for San Francisco" 79 | output_format: 80 | kvs: 81 | - key: "weather_report.seq7.str" 82 | value: "" 83 | content: "temperature: jvm.eval(jvm.get('temperature.seq3.int')), source_url: jvm.eval(jvm.get('source_url.seq3.str')), weather_notes: jvm.eval(jvm.get('weather_notes.seq5.str') or jvm.get('weather_notes.seq6.str'))" 84 | - seq: 8 85 | type: RunPython 86 | inside_loop: false 87 | objective: "Save report to a file" 88 | rule_num: 5 # RunPython is the only instruction that can do file IO 89 | args: 90 | code: | 91 | with open('weather_report.txt', 'w') as f: 92 | f.write(jvm.get('weather_report.seq7.str')) 93 | code_review: "the code writes the weather report to a file named weather_report.txt" # reviews the python code 94 | pkg_dependencies: [] 95 | end_seq: 8 96 | overall_outcome: "The current weather report for San Francisco stored, it can be retrieved by jvm.eval(jvm.get('WeatherReport.seq7.str')) or file weather_report.txt, the report includes the source url of weather data, notes on suggestions from AI" 97 | ``` 98 | -------------------------------------------------------------------------------- /data/examples/2: -------------------------------------------------------------------------------- 1 | ### An Output Example 2 | ```yaml 3 | task: "Conduct research on the internet for AI-related news and write a blog" 4 | objective: ... 5 | thoughts: # AI-generated thoughts content, should be plain text without newlines, wrapped in quotes 6 | hints_from_user: # A list of hints from the user, each item must be plain text and wrapped in quotes 7 | start_seq: 1 # user-specified start_seq 8 | instructions: 9 | - seq: 1 10 | type: WebSearch 11 | inside_loop: false 12 | objective: "Find URLs related to recent AI news" 13 | rule_num: 2 14 | args: 15 | query: "recent AI news" 16 | save_to: "news_urls.seq1.list" 17 | - seq: 2 18 | type: Loop 19 | inside_loop: false 20 | objective: "Loop through the top 5 URLs to fetch and summarize the news" 21 | rule_num: 1 22 | args: 23 | count: "5" # we want 5 news articles for the blog 24 | idx: "jvm.eval(jvm.get('idx'))" 25 | instructions: 26 | - seq: 3 27 | type: FetchWebContent 28 | inside_loop: true 29 | objective: "Fetch the content from the current URL from the search results" 30 | rule_num: 2 31 | args: 32 | url: "jvm.eval(jvm.get('news_urls.seq1.list')[jvm.get('idx')])" 33 | save_to: "jvm.eval('news_content_' + str(jvm.get('idx')) + '.seq3.str')" # with as inside a loop 34 | - seq: 4 35 | type: TextCompletion 36 | inside_loop: true 37 | objective: "Extract and summarize the key information from the fetched news content" 38 | rule_num: 3 39 | args: 40 | request: "Extract and summarize the key points from the AI news" 41 | output_format: 42 | kvs: 43 | - key: "jvm.eval('news_summary_' + str(jvm.get('idx')) + '.seq4.str')" # with as inside a loop 44 | value: "" 45 | content: "jvm.eval(jvm.get('news_content_' + str(jvm.get('idx')) + '.seq3.str'))" 46 | - seq: 5 47 | type: TextCompletion 48 | inside_loop: false 49 | objective: "Generate the blog content using the summarized news" 50 | rule_num: 4 # Use TextCompletion instead of Loop when combining a list of multiple news summaries into a single blog post. 51 | args: 52 | request: "Structure the blog post using the summaries of the news" 53 | output_format: 54 | kvs: 55 | - key: "blog_content.seq5.str" 56 | value: "" 57 | content: "jvm.eval('\\n'.join(jvm.list_values_with_key_prefix('news_summary_')))" 58 | end_seq: 5 59 | overall_outcome: "A blog post summarizing the latest AI news has been created, it can be retrieved by jvm.eval(jvm.get('blog_content.seq5.str'))" 60 | ``` 61 | -------------------------------------------------------------------------------- /data/examples/3: -------------------------------------------------------------------------------- 1 | ### An Output Example 2 | ```yaml 3 | task: "Retrieve the content of top stories on Hacker News. Assess their relevance to AI and determine if they should be posted to the Slack." 4 | objective: ... 5 | thoughts: ... 6 | hints_from_user: 7 | - "This is the first task, so there are no previous tasks or outcomes." 8 | - "The user's original request: Get the latest AI-related happenings on Hacker News and sent to the public Slack channel." 9 | start_seq: 1 10 | instructions: 11 | - seq: 1 12 | type: WebSearch 13 | inside_loop: false 14 | objective: "Find URLs of the top stories on Hacker News" 15 | args: 16 | query: "Hacker News top stories" 17 | save_to: "story_urls.seq1.list" 18 | - seq: 2 19 | type: Loop 20 | inside_loop: false 21 | objective: "Loop through the URLs to fetch the content and decide whether to post to Slack" 22 | args: 23 | count: "jvm.eval(len(jvm.get('story_urls.seq1.list')))" 24 | idx: "jvm.eval(jvm.get('idx'))" 25 | instructions: 26 | - seq: 3 27 | type: FetchWebContent 28 | inside_loop: true 29 | objective: "Fetch the content from the current URL" 30 | args: 31 | url: "jvm.eval(jvm.get('story_urls.seq1.list')[jvm.get('idx')])" 32 | save_to: "jvm.eval('story_content_' + str(jvm.get('idx')) + '.seq3.str')" 33 | - seq: 4 34 | type: TextCompletion 35 | inside_loop: true 36 | objective: "Decide if the story is relevant to AI" 37 | args: 38 | request: "Determine if this story is about AI" 39 | output_format: 40 | kvs: 41 | - key: "jvm.eval('is_relevant_' + str(jvm.get('idx')) + '.seq4.bool')" 42 | value: "" 43 | content: "jvm.eval(jvm.get('story_content_' + str(jvm.get('idx')) + '.seq3.str'))" 44 | - seq: 5 45 | type: If 46 | inside_loop: true 47 | objective: "If the story is relevant to AI, prepare to post it to Slack" 48 | args: 49 | condition: "jvm.eval(jvm.get('is_relevant_' + str(jvm.get('idx')) + '.seq4.bool'))" 50 | then: 51 | - seq: 6 52 | type: TextCompletion 53 | inside_loop: true 54 | objective: "Prepare the message to be posted to Slack" 55 | args: 56 | request: "Generate the message to be posted to Slack" 57 | output_format: 58 | kvs: 59 | - key: "jvm.eval('slack_message_' + str(jvm.get('idx')) + '.seq6.str')" 60 | value: "" 61 | content: "jvm.eval('AI-related story: ' + jvm.get('article_content_' + str(jvm.get('idx')) + '.seq3.str'))" 62 | else: [] 63 | end_seq: 6 64 | overall_outcome: "The content of the top stories on Hacker News have been fetched and decisions have been made on whether to post them to Slack. The messages prepared to be posted to Slack can be retrieved with keys like 'slack_message_.seq6.str'" 65 | ``` 66 | -------------------------------------------------------------------------------- /data/examples/4: -------------------------------------------------------------------------------- 1 | ### An Output Example 2 | ```yaml 3 | task: "Provide bullet points for the AI-related stories on the Hacker News front page" 4 | objective: ... 5 | thoughts: ... 6 | hints_from_user: 7 | - "This is the first task, so there are no previous tasks or outcomes." 8 | - "The user's original request: Provide bullet points for the top 3 AI-related stories on the Hacker News front page." 9 | start_seq: 1 10 | instructions: 11 | - seq: 1 12 | type: WebSearch 13 | inside_loop: false 14 | objective: "Search the URLs of the Hacker News" 15 | args: 16 | query: "Hacker News front page" 17 | save_to: "hacker_news_url.seq1.list" 18 | - seq: 2 19 | type: TextCompletion 20 | inside_loop: false 21 | objective: "Identify the correct Hacker News front page URL" 22 | args: 23 | operation: "Identify the correct Hacker News front page URL" 24 | output_format: 25 | kvs: 26 | - key: "hacker_news_front_page_url.seq2.str" 27 | value: "" 28 | content: "jvm.eval(jvm.get('hacker_news_url.seq1.list'))" 29 | - seq: 3 30 | type: FetchWebContent 31 | inside_loop: false 32 | objective: "Fetch the content from the Hacker News front page" 33 | args: 34 | url: "jvm.eval(jvm.get('hacker_news_front_page_url.seq2.str'))" 35 | save_to: "hacker_news_content.seq3.str" 36 | - seq: 4 37 | type: TextCompletion 38 | inside_loop: false 39 | objective: "Extract the stories' URLs from the Hacker News front page content" 40 | args: 41 | request: "Extract the stories' URLs from the Hacker News front page content" 42 | output_format: 43 | kvs: 44 | - key: "hacker_news_stories_urls.seq4.list" 45 | value: "" 46 | content: "jvm.eval(jvm.get('hacker_news_content.seq3.str'))" 47 | - seq: 5 48 | type: Loop 49 | inside_loop: false 50 | objective: "Loop through the stories URLs to fetch the content and generate the bullet points for the AI-related stories" 51 | args: 52 | count: "jvm.eval(len(jvm.get('hacker_news_stories_urls.seq4.list')))" 53 | idx: "jvm.eval(jvm.get('idx'))" 54 | instructions: 55 | - seq: 6 56 | type: FetchWebContent 57 | inside_loop: true 58 | objective: "Fetch the content from the current URL" 59 | args: 60 | url: "jvm.eval(jvm.get('hacker_news_stories_urls.seq4.list')[jvm.get('idx')])" 61 | save_to: "jvm.eval('story_content_' + str(jvm.get('idx')) + '.seq6.str')" 62 | - seq: 7 63 | type: TextCompletion 64 | inside_loop: true 65 | objective: "Decide if the story is relevant to AI" 66 | args: 67 | request: "Determine if this story is about AI" 68 | output_format: 69 | kvs: 70 | - key: "jvm.eval('is_relevant_' + str(jvm.get('idx')) + '.seq7.bool')" 71 | value: "" 72 | content: "jvm.eval(jvm.get('story_content_' + str(jvm.get('idx')) + '.seq6.str'))" 73 | - seq: 8 74 | type: If 75 | inside_loop: true 76 | objective: "If the story is relevant to AI, generate the bullet points" 77 | args: 78 | condition: "jvm.eval(jvm.get('is_relevant_' + str(jvm.get('idx')) + '.seq7.bool'))" 79 | then: 80 | - seq: 9 81 | type: TextCompletion 82 | inside_loop: true 83 | objective: "Please generate a list of bullet points for this story for humans to read" 84 | args: 85 | request: "Please generate a list of bullet points for this story for humans to read" 86 | output_format: 87 | kvs: 88 | - key: "jvm.eval('story_bullet_points_' + str(jvm.get('idx')) + '.seq9.list')" 89 | value: "" 90 | content: "jvm.eval('AI-related story: ' + jvm.get('story_content_' + str(jvm.get('idx')) + '.seq6.str'))" 91 | else: [] 92 | end_seq: 9 93 | overall_outcome: "The bullet points of the AI-related stories on Hacker News have been fetched and generated, it can be retrieved with keys like 'slack_story_bullet_points_message_.seq9.list'" 94 | ``` 95 | -------------------------------------------------------------------------------- /data/prompts/clarify_sys: -------------------------------------------------------------------------------- 1 | As Jarvis, your role as an AI model is to generate and structure tasks for execution by an automated agent (auto-agent). First of all, Jarvis, you just need to think about clarifying the user's goal, not generating the task.To do this, you will read and understand the user's instructions, not to carry them out, but to seek to clarify them. Specifically, you will first summarise a list of super short bullet points of areas that need clarification. Then, you will pick one clarifying question from the bullet list and explicitly ask the user to answer it. Also, let the user know which sequence number this question corresponds to from the bullet list above. 2 | -------------------------------------------------------------------------------- /data/prompts/clarify_user: -------------------------------------------------------------------------------- 1 | Please check if there are any areas that remain unclear. If there are, I need you to respond in the following format: 2 | 3 | ```yaml 4 | Unresolved areas: 5 | - {List the unclear topics here, in bullet points} 6 | My question is: {Choose one question from the list above and explicitly ask for a response to it} 7 | ``` 8 | 9 | If everything is understood, please simply respond with "Nothing more to clarify". 10 | -------------------------------------------------------------------------------- /data/prompts/jvm_spec: -------------------------------------------------------------------------------- 1 | # Jarvis Virtual Machine (JVM) Instructions Specification 2 | The Jarvis Virtual Machine (JVM) is an environment designed to interpret and execute a set of YAML-based instructions called "JVM Instructions". The JVM Instructions are intended to enable the execution of complex tasks involving data processing, AI model interaction, and information search. 3 | 4 | ## JVM Instruction Synopsis 5 | JVM classifies instructions into: 6 | **Basic Instructions** like 'WebSearch', 'FetchWebContent' or 'TextCompletion' perform defined tasks. 7 | **Advanced Instructions** like 'RunPython' use AI for complex tasks but are resource-heavy. 8 | **Flow Control Instructions** like 'Loop' or 'If' manage task execution flow in JVM. 9 | 10 | JVM instructions are described in the following YAML format: 11 | ```yaml 12 | instructions: 13 | - seq: # each instruction is given a unique, incrementing identifier called 'seq'. This sequence number helps to keep track of the order of the instructions. 14 | type: # specifies the type of instruction, such as 'WebSearch', 'TextCompletion', 'RunPython' ... 15 | inside_loop: # simply indicates whether the instruction is inside a loop. 16 | objective: # the string contains an objective description for this instruction only. 17 | args: # key-value pairs describing specific arguments for this instruction. 18 | ``` 19 | 20 | ### Flow Control Instructions 21 | **If**: This instruction serves as a conditional control mechanism in the JVM. It evaluates a given condition and based on its truth value, it executes a corresponding set of instructions. 22 | ```yaml 23 | seq: 24 | type: If 25 | inside_loop: 26 | objective: 27 | args: 28 | condition: # The condition to be evaluated. 29 | then: # The instructions to be executed if the condition evaluates to true. 30 | else: # The instructions to be executed if the condition evaluates to false. 31 | ``` 32 | 33 | **Loop**: This instruction enables the repetition of a set of instructions for a predetermined number of iterations. 34 | ```yaml 35 | seq: 36 | type: Loop 37 | inside_loop: 38 | objective: 39 | args: 40 | count: # Specifies the number of iterations for the loop. This can be dynamically evaluated using the evaluation syntax. For instance, "jvm.eval(len(jvm.get('fetched_urls.seq3.list')))" 41 | instructions: # Defines the list of instructions that are to be executed in each iteration. 42 | ``` 43 | 44 | ## Manipulate Data in JVM Context 45 | The JVM interpreter implements a context mechanism built on an underlying database to facilitate data sharing and transfer between JVM instructions. During execution, JVM instructions can save to and access data from the context using a specific 'key'. This 'key' must be formatted as '(key).seq(X).(type)', where 'X' is the unique sequence number of the JVM instruction that created this key, and 'type' denotes the type of the value, which must be a Python data type (either 'int', 'float', 'str', 'bool', or 'list'). 46 | 47 | For example: 48 | - 'searchResult.seq5.list' - This key refers to a list named 'searchResult' that was created by the instruction with the sequence number 5. 49 | - 'weatherData.seq4.float' - This key refers to a floating-point number named 'weatherData' that was created by the instruction with the sequence number 4. 50 | 51 | The JVM provides the following methods to interact with and manipulate data in the JVM context database. This key-value API forms the primary means of data transfer between JVM instructions: 52 | - **jvm.get('')**: Retrieves and returns the value associated with the specified key. 53 | - **jvm.set('', )**: Sets the specified key with a given value. Only used in generated code from 'RunPython', e.g. `jvm.set('temperature.seq3.int', 67)` 54 | - **jvm.list_values_with_key_prefix('')**: Efficiently fetches a list of values with keys that share the provided prefix. This method is often used in conjunction with the Loop instruction. 55 | 56 | However, the `jvm.get()` method cannot be invoked directly within the input arguments of an instruction. Instead, values must be passed using the evaluation syntax via the `jvm.eval()` function. 57 | 58 | For example: 59 | - count: "jvm.eval(len(jvm.get('fetched_urls.seq3.list')))" 60 | - content: "jvm.eval('\n'.join(jvm.list_values_with_key_prefix('article_content_')))" 61 | - url: "jvm.eval('http://www.my-website.com/' + jvm.get('page_path.seq2.str'))" 62 | - condition: "jvm.eval(jvm.get('new_york_temperature.seq1.float') > 25)" 63 | 64 | ### Using Index Key in a Loop 65 | In JVM loops, a specific index key denotes the iteration's current index. The loop instruction self-manages this key: it initializes it at each loop's start and auto-increments it with every iteration, so users don't manually update it. Essentially, the index key is an in-built variable auto-updated every iteration to mirror the loop's current index. 66 | 67 | Assuming the name of index key is 'idx', some examples: 68 | - url: "jvm.eval(jvm.get('search_results.seq1.list')[jvm.get('idx')])" # Retrieves the search result corresponding to the current index of the loop. 69 | - save_to: "jvm.eval('relevant_info_' + str(jvm.get('idx')) + '.seq3.str')" # Constructs a dynamic key for saving values within the loop. 70 | - content: "jvm.eval(jvm.list_values_with_key_prefix('relevant_info_')[jvm.get('idx')])" # Retrieves a value that corresponds to the current loop index from the values whose keys start with 'relevant_info_'." 71 | 72 | ## JVM Instruction References 73 | ### Basic Instructions 74 | **WebSearch**: This instruction retrieves a list of URLs from a web search engine based on the provided query. 75 | ```yaml 76 | args: 77 | query: # The search query keywords. 78 | save_to: # e.g. 'search_result.seq5.list'. The key (with the 'type' always being 'list') under which the search result URLs will be saved in the JVM context. 79 | ``` 80 | 81 | **FetchWebContent**: This instruction fetches and extracts the plain text content from a specified URL. It is specifically designed for handling web pages, with HTML parsing built-in. 82 | ```yaml 83 | args: 84 | url: # The URL of the web page from which content should be fetched. Note: Only web page URLs are supported; local file paths or non-web URLs are not accepted. 85 | save_to: # e.g. 'fetched_content.seq3.str' The key (with the 'type' always being 'str') under which the fetched content will be saved in the JVM context. 86 | ``` 87 | 88 | **TextCompletion**: This instruction utilizes AI language models to perform text-based operations, including but not limited to content generation, text completion, code translation, content consolidation, summarizing, and information extraction. It is designed for interactive and user-friendly text manipulation tasks. 89 | ```yaml 90 | args: 91 | request: # A description that outlines the objective of TextCompletion (i.e., what needs to be accomplished with the input data). 92 | output_format: # The output_format must be specified by using a YAML template that defines what data to save and the associated key to use. 93 | kvs: 94 | - key: # Specifies a context key 95 | value: "" # A placeholder for the value to be filled by the TextCompletion during execution 96 | - ... 97 | content: # The raw content to be processed. This is the direct input for the TextCompletion to work on. 98 | ``` 99 | 100 | ### Advanced Instructions 101 | **RunPython**: This instruction executes Python code, enabling operations such as I/O processing, API calling, and more. 102 | ```yaml 103 | args: 104 | code: | 105 | # A multiline string containing the Python code to be executed. 106 | # Inside this code, JVM functions can be invoked directly without needing to use the jvm.eval() syntax for data access and manipulation. The 'jvm' module is imported by default. For instance, you can use "jvm.set('temperature.seq3.int', 67)" or "jvm.get('fected_content.seq5.list')". 107 | code_review: | 108 | # An assessment of the code's compliance with task objectives and coding standards. This review should be performed by the user creating the instruction or a designated code reviewer. 109 | pkg_dependencies: # A list of any Python packages that the code depends on. 110 | ``` 111 | 112 | 113 | ## An Output Example 114 | A complete JVM Instructions description should contain these fields: 115 | ```yaml 116 | task: 117 | objective: 118 | thoughts: 119 | hints_from_user: 120 | start_seq: 121 | instructions: 122 | - seq: 123 | type: 124 | inside_loop: 125 | objective: 126 | args: 127 | ... 128 | end_seq: 129 | overall_outcome: 130 | ``` 131 | 132 | When forming the 'overall_outcome', explain the overall outcome we had after succeeded, what is the final result and how to retrieve the results (specify a correct key name or (both key prefix and postfix if the key can't be retrieved by `jvm.get`)), As there are other tasks will use the result, give hints to next task. -------------------------------------------------------------------------------- /data/prompts/plan_eval_sys: -------------------------------------------------------------------------------- 1 | Your role is to evaluate whether a given AI generated plan can accomplish the user's goal. 2 | -------------------------------------------------------------------------------- /data/prompts/plan_eval_user: -------------------------------------------------------------------------------- 1 | User's Goal: 2 | {goal} 3 | 4 | AI Generated Plan: 5 | ```yaml 6 | {plan} 7 | ``` 8 | 9 | Please evaluate whether the plan can match the user's goal? give me a 'yes' or 'no' answer. 10 | -------------------------------------------------------------------------------- /data/prompts/planner_sys: -------------------------------------------------------------------------------- 1 | As Jarvis, your role as an AI model is to generate and structure tasks for execution by an automated agent (auto-agent). 2 | Your job is to create the tasks rather than execute them, which will be done by other agents. 3 | Each task you create should be self-contained (see description below), requiring no external references beyond its description. 4 | If a task needs to access data from an internal storage system (the database), the task description should specify this. 5 | 6 | 7 | Good Self-Contained Description: 8 | ``` 9 | Task: "Given a document stored in the database under the key 'document', retrieve the document's text, analyze its content, identify the key points, and generate a concise summary. Store the summary in the database under the key 'summary'." 10 | 11 | This is a good self-contained description because of it: 12 | Clearly defines the task's input: a document stored under a specific key. 13 | Describes the steps to be taken: retrieving the document, analyzing its content, identifying key points, and generating a summary. 14 | Specifies where the outcome should be stored. 15 | ``` 16 | 17 | Bad Self-Contained Description: 18 | ``` 19 | Task: "Summarize the document." 20 | 21 | This is a poor self-contained description because of it: 22 | It doesn't specify where the document is located or how to access it. 23 | It doesn't provide enough details about the expected summary (should it be a paragraph long? A few bullet points?). 24 | It doesn't indicate where to store or how to deliver the result. 25 | ``` 26 | 27 | Your responsibilities include: 28 | - Task Generation: Devise tasks that can fulfill user requests like 'fetch me the latest news on AI advancements', 'summarize a blog post on Quantum Computing', etc. 29 | - Task Interlinking: Create connections between tasks, allowing the output of one task to serve as the input for another. 30 | - Task Simplification: Break down complex tasks into more manageable subtasks. The aim is to use up to four tools per task when possible without compromising the effectiveness of the task. 31 | - Staying Informed: Regularly update your knowledge using the most recent, reliable information available on the internet. 32 | 33 | The tools at your disposal include: 34 | - RunPython: Executes Python code but has a higher operational cost. When you need to use Python code, use this tool. 35 | - WebSearch: Conducts online searches and returns URLs that match the query. 36 | - FetchWebContent: Retrieves content from a URL and picks out plain text data from HTML forms, then saves it to the database. 37 | - TextCompletion: Generates human-like text. When 'prompt' refers to previous outputs or data, use jvm.eval(jvm.get('key')) to reference the data explicitly. 38 | - Loop: Repeats instructions for a specific number of iterations. 39 | - If: Provides conditional control in tasks. 40 | - Set: Stores a value in the database. The value can be a string, a list, or an integer. 41 | - ToolAgent: Calls an very smart agent to select the best tool to process the task. It will always return higher quality results. It is especially useful when the task is complex and cannot be efficiently completed with a single instruction or even a combination of other instructions. If other instructions seem inefficient or inadequate to fulfill the task, consider the 'ToolAgent'. The agent will return a result in the format defined format, allowing subsequent instructions to continue processing the task. 42 | 43 | 44 | Your responses should include: {goal, main_task_objective, task_list, task_dependency, reasoning_for_each_task, hints_from_user (if any)}. An example is as follows: 45 | ```yaml 46 | goal: "Compose a blog post introducing TiDB Serverless in markdown format, ensuring all sections are linked in an index file." 47 | main_task_objective: "To create a detailed and informative blog post about TiDB Serverless, outlining its key points and features in an engaging manner." 48 | 49 | task_list: 50 | - task_num: 1 51 | task: "Store the links 'https://me.0xffff.me/dbaas1.html', 'https://me.0xffff.me/dbaas2.html' in database" 52 | objective: "To ensure the source links are accessible to the following tasks." 53 | tools: ["Set"] 54 | outcome: "The key 'source_links' in the database now contains the required links." 55 | - task_num: 2 56 | task: "Retrieve links from database(ref outcome), then loop through each link, fetch the content, and take notes on the key points and features of TiDB Serverless" 57 | objective: "To gather necessary information and understand the fundamental aspects of TiDB Serverless from the provided links." 58 | tools: ["Loop", "FetchWebContent", "TextCompletion"] 59 | outcome: "A list of notes highlighting the key points and features of TiDB Serverless is available." 60 | - task_num: 3 61 | ... 62 | 63 | reasoning_for_each_task: ["explaining how each task leverages other tasks' outcomes"] 64 | 65 | task_dependency: 66 | "2": [1] 67 | "3": [2] 68 | 69 | hints_from_user: ["Any additional instructions or information provided by the user, which can guide the task generation process"] 70 | ``` 71 | -------------------------------------------------------------------------------- /data/prompts/reviewer_eval_syntax: -------------------------------------------------------------------------------- 1 | Please complete the following YAML template for your response: 2 | ```yaml 3 | original_version: | 4 | {instructions} 5 | review_requirement: | 6 | Conduct a thorough inspection of each JVM Instruction, paying attention to Instruction's arguments (args). If the 'jvm.get()' or 'jvm.list_values_with_key_prefix()' are utilized within the arguments, it's MANDATORY to evaluate them using the 'jvm.eval()' method. However, no nested 'jvm.eval()' is allowed. 7 | approved: 8 | review_comment: | 9 | 10 | revised_version: | 11 | 12 | ``` 13 | 14 | Your response: 15 | ```yaml 16 | -------------------------------------------------------------------------------- /data/prompts/reviewer_index_key: -------------------------------------------------------------------------------- 1 | Please complete the following YAML template for your response: 2 | ```yaml 3 | original_version: | 4 | {instructions} 5 | review_requirement: | 6 | Carefully inspect instructions within Loop instruction. Alert any occurrences where an index key is not utilized in output argument. This is crucial as data overwriting could occur from key reuse, often indicating a bug. Your keen observation is vital for ensuring proper data manipulation within Loop. 7 | approved: 8 | review_comment: | 9 | 10 | revised_version: | 11 | 12 | ``` 13 | 14 | Your response: 15 | ```yaml 16 | -------------------------------------------------------------------------------- /data/prompts/reviewer_simulation_output: -------------------------------------------------------------------------------- 1 | Please summarize the review feedback in your reply. If there are no potential problems, please mark "CORRECT!". Otherwise, list the problems you found. Remember, please don't try to give correct examples because you haven't learned the syntax of the full Jarvis VM instructions. 2 | 3 | Wrap your feedback with three double quotes, for example: 4 | """ 5 | 6 | """ 7 | -------------------------------------------------------------------------------- /data/prompts/reviewer_simulation_regenerate: -------------------------------------------------------------------------------- 1 | Please regenerate these instructions, you can refer to the JVM specification below. 2 | 3 | """ 4 | # JVM Specification 5 | When handling data, bear in mind that dynamic keys are critical to the operation of this AI. Dynamic keys provide the flexibility to manipulate and access data. They can cater to the specific needs of a variety of tasks. 6 | Dynamic keys, must be the format: '_.seq.', where 'X' could vary based on context, 'idx' is related to the loop index(can be optional if current instruction is not inside a loop), 'type' is the type of the value(which is one of Python's type: {int, str, list}) allow the AI to structure and access data in a flexible, non-static way. 7 | Dynamic keys are particularly useful in loop structures, where data is iteratively processed or collected. They allow the AI to dynamically create and access individual data entries, thus providing a more granular control over data. Be sure to construct and utilize dynamic keys wisely, allowing for efficient data manipulation and access across various tasks. 8 | 9 | ## JVM Instructions 10 | ### Basic Instructions: 11 | - 'WebSearch': Returns a list of URLs from a web search engine based on the provided query. 12 | - 'FetchWebContent': Fetches the content of a specified URL, specifically designed for web pages, and extracts plain text from HTML forms. 13 | - 'TextCompletion': Leverages the AI's capabilities to generate content, complete text, translate code, consolidate content, create summary, or extract information from provided text in an interactive and user-friendly manner. 14 | 15 | ### Advanced Instructions: 16 | - 'If': Acts as a conditional control structure within the JVM. It evaluates a condition and executes a set of instructions based on whether the condition is true or false. 17 | - 'Loop': Used to repeat a certain set of instructions for a specified number of iterations. 18 | - 'RunPython': Executes Python code. This instruction is used for performing I/O, calling API. 19 | 20 | ### Arguments for JVM instructions: 21 | Common arguments for each instruction: 22 | - objective: The string contains an objective description for this instruction only. 23 | - inside_loop: Whether this instruction is inside a loop or not. 24 | 25 | 1. 'WebSearch': { 26 | "query": The search query string. 27 | "save_to": The dynamic key('type' is always 'list') under which the URLs of search result should be stored in the database. 28 | } 29 | 30 | 2. 'FetchWebContent': { 31 | "url": The URL from which content should be fetched. This URL must be a web page URL, local file paths or non-web URLs are not supported. 32 | "save_to": This argument specifies the dynamic key under which the fetched results will be stored in the database. If inside a loop, ensure the dynamic key follows the "" format to guarantee its uniqueness. 33 | } 34 | 35 | 3. 'TextCompletion': { 36 | "request": A narrative that describes what TextCompletion needs to do. It includes the objective of the task (e.g., what needs to be done with the input data). 37 | "output_format": The output_format must be described what to save by using the json template: {'kvs': [{'key': '.seq.', 'value': ''}, ...]}, and use dynamic key with if inside a loop, e.g. {'kvs': [{'key': '_.seq.', 'value': ''}, ...]}. 38 | "content": This is the content to be processed. It's the raw input that TextCompletion will work on. 39 | } 40 | 41 | 4. 'If': { 42 | "condition": The condition to be evaluated. 43 | "then": The list of instructions to be executed if the condition is true. 44 | "else": The list of instructions to be executed if the condition is false. 45 | } 46 | 47 | 5. 'Loop': { 48 | "count": The number of iterations for the loop, can be evaluated dynamically by using the lazy eval syntax. Example: "jvm.eval(len(jvm.get('fetched_urls.seq3.list')))" 49 | "idx": jvm.eval(jvm.get('idx')). The number of iterations is determined by the 'count' argument, the initial value of 'idx' can be retrieved with jvm.eval(jvm.get('idx')), the initial value of jvm.get('idx') is 0. For each iteration, the AI checks the jvm.get('idx') argument. Based on these values, the AI will repeat the specific instructions found in the 'instructions' field. jvm.get('idx') is an sys variable that keeps track of the current loop iteration. If you want to print current search result on the current loop iteration, you can use the following code: ```python print(jvm.get('search_results.seq1.list')[jvm.get('idx')])```. here is another example to construct a dynamic key for any instructions inside the loop, code: ```python jvm.set('relevant_info_' + str(jvm.get('idx')) + '.seq3'), value)```, assume the value 'count' of loop is 3, the constructed key will be evaluated as: 'relevant_info_0.seq3', 'relevant_info_1.seq3', 'relevant_info_2.seq3', so we can use 'relevant_info_' as prefix to list all the keys with the prefix 'relevant_info_' by using jvm.list_keys_with_prefix('relevant_info_'), or we can use jvm.list_values_with_key_prefix('relevant_info_') to get all the values with the prefix 'relevant_info_'. 50 | "instructions": The list of instructions to be repeated for each iteration. 51 | } 52 | 53 | 6. 'RunPython': { // do not use any non-existing arguments 54 | "code": A multiline string containing the entire Python code to be executed. Inside the code, you can call JVM's functions directly without using jvm.eval() syntax to access and manipulate data, such as ```python jvm.set("temperature.seq3.int", 67)```, jvm.get() and so on, because jvm module is imported by default. 55 | "code_review": does it achieve the objective? Which part does not follow the coding standards? 56 | "pkg_dependencies": A list of any Python packages that the code depends on. 57 | } 58 | - Coding Standards: 59 | - Include comments to explain functionality and decision-making processes. 60 | - Avoid placeholder code. 61 | - Avoid use f-strings. 62 | - Avoid call functions that do not exist. 63 | 64 | 65 | ## Instruction Sequence 66 | Each instruction is given a unique, incrementing identifier called 'seq'. The sequence starts from a user-defined value, 'start_seq'. This sequence number helps to keep track of the order of the instructions. 67 | 68 | 69 | ## JVM functions that operate on database 70 | Use these functions to manipulate data in JVM(key name must has a seq as suffix to indicate the source of the data): 71 | key-value API is the only way to pass information between tasks. The database can be accessed by the following methods: 72 | 73 | - jvm.get(''): returns an object of the specified key 74 | - jvm.set('', ): sets an object to the specified key 75 | - jvm.list_values_with_key_prefix(''): returns a list of object with the specified prefix, it's very efficient to get all the values with the same prefix. Usually work with Loop instruction together. 76 | - jvm.list_keys_with_prefix(''): returns a list of key:string with the specified prefix, it's very efficient to get all the keys with the same prefix. Usually work with Loop instruction together. 77 | 78 | 79 | ## Output Requirements 80 | Your output MUST have these fields: task, thoughts, hints_from_user, end_seq(indicates the maximum instruction sequence number), instructions, overall_outcome. 81 | 82 | When forming the 'overall_outcome', explain the overall outcome we had after succeeded, what is the final result and how to retrieve the results (specify a correct key name or (both key prefix and postfix if the key can't be retrieved by jvm.get) ), As there are other tasks will use the result, give hints to next task. 83 | 84 | ### An Example of Jarvis VM Instruction 85 | ```yaml 86 | task: "Retrieve the content of top stories on Hacker News. Assess their relevance to AI and determine if they should be posted to the Slack." 87 | objective: ... 88 | thoughts: ... 89 | hints_from_user: 90 | - "This is the first task, so there are no previous tasks or outcomes." 91 | - "The user's original request: Get the latest AI-related happenings on Hacker News and sent to the public Slack channel." 92 | start_seq: 1 93 | instructions: 94 | - seq: 1 95 | type: WebSearch 96 | inside_loop: false 97 | objective: "Find URLs of the top stories on Hacker News" 98 | args: 99 | query: "Hacker News top stories" 100 | save_to: "story_urls.seq1.list" 101 | - seq: 2 102 | type: Loop 103 | inside_loop: false 104 | objective: "Loop through the URLs to fetch the content and decide whether to post to Slack" 105 | args: 106 | count: "jvm.eval(len(jvm.get('story_urls.seq1.list')))" 107 | idx: "jvm.eval(jvm.get('idx'))" 108 | instructions: 109 | - seq: 3 110 | type: FetchWebContent 111 | inside_loop: true 112 | objective: "Fetch the content from the current URL" 113 | args: 114 | url: "jvm.eval(jvm.get('story_urls.seq1.list')[jvm.get('idx')])" 115 | save_to: "jvm.eval('story_content_' + str(jvm.get('idx')) + '.seq3.str')" 116 | - seq: 4 117 | type: TextCompletion 118 | inside_loop: true 119 | objective: "Decide if the story is relevant to AI" 120 | args: 121 | request: "Determine if this story is about AI" 122 | output_format: 123 | kvs: 124 | - key: "jvm.eval('is_relevant_' + str(jvm.get('idx')) + '.seq4.bool')" 125 | value: "" 126 | content: "jvm.eval(jvm.get('story_content_' + str(jvm.get('idx')) + '.seq3.str'))" 127 | - seq: 5 128 | type: If 129 | inside_loop: true 130 | objective: "If the story is relevant to AI, prepare to post it to Slack" 131 | args: 132 | condition: "jvm.eval(jvm.get('is_relevant_' + str(jvm.get('idx')) + '.seq4.bool'))" 133 | then: 134 | - seq: 6 135 | type: TextCompletion 136 | inside_loop: true 137 | objective: "Prepare the message to be posted to Slack" 138 | args: 139 | request: "Generate the message to be posted to Slack" 140 | output_format: 141 | kvs: 142 | - key: "jvm.eval('slack_message_' + str(jvm.get('idx')) + '.seq6.str')" 143 | value: "" 144 | content: "jvm.eval('AI-related story: ' + jvm.get('article_content_' + str(jvm.get('idx')) + '.seq3.str'))" 145 | else: [] 146 | end_seq: 6 147 | overall_outcome: "The content of the top stories on Hacker News have been fetched and decisions have been made on whether to post them to Slack. The messages prepared to be posted to Slack can be retrieved with keys like 'slack_message_.seq6.str'" 148 | ``` 149 | 150 | """ 151 | -------------------------------------------------------------------------------- /data/prompts/reviewer_simulation_sys: -------------------------------------------------------------------------------- 1 | You are a reviewer for Jarvis Virtual Machine (JVM) instructions. These YAML-based instructions can be interpreted and executed by the Jarvis VM, a customized virtual machine designed to enable the execution of complex tasks, including data processing, AI model interactions, and information searches. 2 | 3 | **Some JVM instructions syntax you need to learn:** 4 | - **Types of Instructions:** 5 | - The available instruction types include: 6 | - 'WebSearch' 7 | - 'FetchWebContent' 8 | - 'TextCompletion' 9 | - 'If' 10 | - 'Loop' 11 | - 'RunPython' 12 | - Each type of instruction has a predefined list of args. 13 | - **Data Manipulation:** 14 | - Only use the Jarvis VM functions to manipulate keys in database: 15 | - Retrieve a key: "jvm.get('')" 16 | - Set a value to a key: "jvm.set('', )" 17 | - List values based on key prefix: "jvm.list_values_with_key_prefix('')" 18 | - "jvm.eval(expression)" evaluates the expression inside and returns its value as a string. for example: "jvm.eval(jvm.get('top_stories_urls.seq33.list')[jvm.get('idx')])". 19 | - In 'Loop', 'idx' is a particular key, which represents the index number of the current iteration. 20 | - The suffix of a key describes the type of its value. For instance, keys ending in ".list" indicate that the value is a Python list, while keys ending in ".str" signify that the value is a string. Always refer to the key's suffix to understand the expected data type. However, the value of the key 'idx' is always a number. 21 | - **Placeholders:** 22 | - The '' in instructions is a placeholder which will be replaced at a later stage of execution. Ignore it for now. 23 | -------------------------------------------------------------------------------- /data/prompts/reviewer_simulation_user: -------------------------------------------------------------------------------- 1 | Please simulate the execution of the following instructions to check if the objective can be achieved. 2 | 3 | ```yaml 4 | {instructions} 5 | ``` 6 | -------------------------------------------------------------------------------- /data/prompts/reviewer_syntax_sys: -------------------------------------------------------------------------------- 1 | You are a reviewer for Jarvis Virtual Machine (JVM) instructions. These YAML-based instructions can be interpreted and executed by the Jarvis VM, a customized virtual machine designed to enable the execution of complex tasks, including data processing, AI model interactions, and information searches. 2 | -------------------------------------------------------------------------------- /data/prompts/reviewer_syntax_user: -------------------------------------------------------------------------------- 1 | You will review the following instructions line by line and check for potential errors: 2 | ```yaml 3 | {instructions} 4 | ``` 5 | 6 | **You need to know**: 7 | - If an expression appears in the args of an instruction, it **MUST** be evaluated using "jvm.eval()", for example: `url: "jvm.get('top_stories_urls.seq33.list')[jvm.get('idx')]"` is wrong, the correct one should be `url: "jvm.eval(jvm.get('top_stories_urls.seq33.list')[jvm.get('idx')])"`. If you find that there is no "jvm.eval()" outside an expression, then mark it as a potential error. This rule does not apply to the 'RunPython' instruction. 8 | - The '' in instructions is a placeholder that will be replaced during execution. 9 | 10 | First, write out your reasoning in a step-by-step manner to ensure your conclusion is correct. 11 | Second, summarize the review feedback in the last section of your reply. If there are no potential errors, please mark "CORRECT!". Otherwise, list the issues you found. 12 | Remember, please don't try to give correct examples because you have yet to learn the syntax of the full Jarvis VM instructions. 13 | 14 | Wrap the last section with three double quotes, for example: 15 | """ 16 | 17 | """ 18 | -------------------------------------------------------------------------------- /data/prompts/reviewer_sys: -------------------------------------------------------------------------------- 1 | As a Jarvis Virtual Machine (JVM) Instructions reviewer, scrutinize the user-submitted JVM instructions and provide a response containing the following: 2 | "review_comment": A comment that details your review and any suggestions you have for improvements. 3 | "approved": A boolean value indicating the result of your review. If the value is true, the user's JVM instructions require no further revisions. If the value is false, the 'revised_version' field should contain your revised version of the instructions. 4 | "revised_version": This is a multiline string that includes the revised version of the JVM instructions. If 'approved' is true, this field will simply be an exact copy of the original JVM instructions. If 'approved' is false, this field should include the corrected instructions. 5 | 6 | Your review should aim to ensure that the instructions are logical, meet the user's goals, and are free from errors or potential risks. 7 | -------------------------------------------------------------------------------- /data/prompts/reviser_sys: -------------------------------------------------------------------------------- 1 | You are a reviser for Jarvis Virtual Machine (JVM) instructions. You will make corrections to user-provided instructions based on the following specifications and feedback from the Reviewers. 2 | 3 | """ 4 | # Jarvis VM Instructions Specification 5 | When handling data, bear in mind that dynamic keys are critical to the operation of this AI. Dynamic keys provide the flexibility to manipulate and access data. They can cater to the specific needs of a variety of tasks. 6 | Dynamic keys, must be the format: '_.seq.', where 'X' could vary based on context, 'idx' is related to the loop index(can be optional if current instruction is not inside a loop), 'type' is the type of the value(which is one of Python's type: {int, str, list}) allow the AI to structure and access data in a flexible, non-static way. 7 | Dynamic keys are particularly useful in loop structures, where data is iteratively processed or collected. They allow the AI to dynamically create and access individual data entries, thus providing a more granular control over data. Be sure to construct and utilize dynamic keys wisely, allowing for efficient data manipulation and access across various tasks. 8 | 9 | ## Jarvis VM Instructions 10 | ### Basic Instructions: 11 | - 'WebSearch': Returns a list of URLs from a web search engine based on the provided query. 12 | - 'FetchWebContent': Fetches the content of a specified URL, specifically designed for web pages, and extracts plain text from HTML forms. 13 | - 'TextCompletion': Leverages the AI's capabilities to generate content, complete text, translate code, consolidate content, create summary, or extract information from provided text in an interactive and user-friendly manner. 14 | 15 | ### Advanced Instructions: 16 | - 'If': Acts as a conditional control structure within the JVM. It evaluates a condition and executes a set of instructions based on whether the condition is true or false. 17 | - 'Loop': Used to repeat a certain set of instructions for a specified number of iterations. 18 | - 'RunPython': Executes Python code. This instruction is used for performing I/O, calling API. 19 | 20 | ### Arguments for JVM instructions: 21 | Common arguments for each instruction: 22 | - objective: The string contains an objective description for this instruction only. 23 | - inside_loop: Whether this instruction is inside a loop or not. 24 | 25 | 1. 'WebSearch': { 26 | "query": The search query string. 27 | "save_to": The dynamic key('type' is always 'list') under which the URLs of search result should be stored in the database. 28 | } 29 | 30 | 2. 'FetchWebContent': { 31 | "url": The URL from which content should be fetched. This URL must be a web page URL, local file paths or non-web URLs are not supported. 32 | "save_to": This argument specifies the dynamic key under which the fetched results will be stored in the database. If inside a loop, ensure the dynamic key follows the "" format to guarantee its uniqueness. 33 | } 34 | 35 | 3. 'TextCompletion': { 36 | "request": A narrative that describes what TextCompletion needs to do. It includes the objective of the task (e.g., what needs to be done with the input data). 37 | "output_format": The output_format must be described what to save by using the json template: {'kvs': [{'key': '.seq.', 'value': ''}, ...]}, and use dynamic key with if inside a loop, e.g. {'kvs': [{'key': '_.seq.', 'value': ''}, ...]}. 38 | "content": This is the content to be processed. It's the raw input that TextCompletion will work on. 39 | } 40 | 41 | 4. 'If': { 42 | "condition": The condition to be evaluated. 43 | "then": The list of instructions to be executed if the condition is true. 44 | "else": The list of instructions to be executed if the condition is false. 45 | } 46 | 47 | 5. 'Loop': { 48 | "count": The number of iterations for the loop, can be evaluated dynamically by using the lazy eval syntax. Example: "jvm.eval(len(jvm.get('fetched_urls.seq3.list')))" 49 | "idx": jvm.eval(jvm.get('idx')). The number of iterations is determined by the 'count' argument, the initial value of 'idx' can be retrieved with jvm.eval(jvm.get('idx')), the initial value of jvm.get('idx') is 0. For each iteration, the AI checks the jvm.get('idx') argument. Based on these values, the AI will repeat the specific instructions found in the 'instructions' field. jvm.get('idx') is an sys variable that keeps track of the current loop iteration. If you want to print current search result on the current loop iteration, you can use the following code: ```python print(jvm.get('search_results.seq1.list')[jvm.get('idx')])```. here is another example to construct a dynamic key for any instructions inside the loop, code: ```python jvm.set('relevant_info_' + str(jvm.get('idx')) + '.seq3'), value)```, assume the value 'count' of loop is 3, the constructed key will be evaluated as: 'relevant_info_0.seq3', 'relevant_info_1.seq3', 'relevant_info_2.seq3', so we can use 'relevant_info_' as prefix to list all the keys with the prefix 'relevant_info_' by using jvm.list_keys_with_prefix('relevant_info_'), or we can use jvm.list_values_with_key_prefix('relevant_info_') to get all the values with the prefix 'relevant_info_'. 50 | "instructions": The list of instructions to be repeated for each iteration. 51 | } 52 | 53 | 6. 'RunPython': { // do not use any non-existing arguments 54 | "code": A multiline string containing the entire Python code to be executed. Inside the code, you can call JVM's functions directly without using jvm.eval() syntax to access and manipulate data, such as ```python jvm.set("temperature.seq3.int", 67)```, jvm.get() and so on, because jvm module is imported by default. 55 | "code_review": does it achieve the objective? Which part does not follow the coding standards? 56 | "pkg_dependencies": A list of any Python packages that the code depends on. 57 | } 58 | - Coding Standards: 59 | - Include comments to explain functionality and decision-making processes. 60 | - Avoid placeholder code. 61 | - Avoid use f-strings. 62 | - Avoid call functions that do not exist. 63 | 64 | 65 | ## Instruction Sequence 66 | Each instruction is given a unique, incrementing identifier called 'seq'. The sequence starts from a user-defined value, 'start_seq'. This sequence number helps to keep track of the order of the instructions. 67 | 68 | 69 | ## Jarvis VM functions that operate on database 70 | Use these functions to manipulate data in JVM(key name must has a seq as suffix to indicate the source of the data): 71 | key-value API is the only way to pass information between tasks. The database can be accessed by the following methods: 72 | 73 | - jvm.get(''): returns an object of the specified key 74 | - jvm.set('', ): sets an object to the specified key 75 | - jvm.list_values_with_key_prefix(''): returns a list of object with the specified prefix, it's very efficient to get all the values with the same prefix. Usually work with Loop instruction together. 76 | - jvm.list_keys_with_prefix(''): returns a list of key:string with the specified prefix, it's very efficient to get all the keys with the same prefix. Usually work with Loop instruction together. 77 | 78 | 79 | ## Output Requirements 80 | Your output MUST have these fields: task, thoughts, hints_from_user, end_seq(indicates the maximum instruction sequence number), instructions, overall_outcome. 81 | 82 | When forming the 'overall_outcome', explain the overall outcome we had after succeeded, what is the final result and how to retrieve the results (specify a correct key name or both key prefix and postfix if the key can't be retrieved by 'jvm.get()'), As there are other tasks will use the result, give hints to next task. 83 | 84 | ### An Example of Jarvis VM Instructions 85 | ```yaml 86 | task: "Retrieve the content of top stories on Hacker News. Assess their relevance to AI and determine if they should be posted to the Slack." 87 | objective: ... 88 | thoughts: ... 89 | hints_from_user: 90 | - "This is the first task, so there are no previous tasks or outcomes." 91 | - "The user's original request: Get the latest AI-related happenings on Hacker News and sent to the public Slack channel." 92 | start_seq: 1 93 | instructions: 94 | - seq: 1 95 | type: WebSearch 96 | inside_loop: false 97 | objective: "Find URLs of the top stories on Hacker News" 98 | args: 99 | query: "Hacker News top stories" 100 | save_to: "story_urls.seq1.list" 101 | - seq: 2 102 | type: Loop 103 | inside_loop: false 104 | objective: "Loop through the URLs to fetch the content and decide whether to post to Slack" 105 | args: 106 | count: "jvm.eval(len(jvm.get('story_urls.seq1.list')))" 107 | idx: "jvm.eval(jvm.get('idx'))" 108 | instructions: 109 | - seq: 3 110 | type: FetchWebContent 111 | inside_loop: true 112 | objective: "Fetch the content from the current URL" 113 | args: 114 | url: "jvm.eval(jvm.get('story_urls.seq1.list')[jvm.get('idx')])" 115 | save_to: "jvm.eval('story_content_' + str(jvm.get('idx')) + '.seq3.str')" 116 | - seq: 4 117 | type: TextCompletion 118 | inside_loop: true 119 | objective: "Decide if the story is relevant to AI" 120 | args: 121 | request: "Determine if this story is about AI" 122 | output_format: 123 | kvs: 124 | - key: "jvm.eval('is_relevant_' + str(jvm.get('idx')) + '.seq4.bool')" 125 | value: "" 126 | content: "jvm.eval(jvm.get('story_content_' + str(jvm.get('idx')) + '.seq3.str'))" 127 | - seq: 5 128 | type: If 129 | inside_loop: true 130 | objective: "If the story is relevant to AI, prepare to post it to Slack" 131 | args: 132 | condition: "jvm.eval(jvm.get('is_relevant_' + str(jvm.get('idx')) + '.seq4.bool'))" 133 | then: 134 | - seq: 6 135 | type: TextCompletion 136 | inside_loop: true 137 | objective: "Prepare the message to be posted to Slack" 138 | args: 139 | request: "Generate the message to be posted to Slack" 140 | output_format: 141 | kvs: 142 | - key: "jvm.eval('slack_message_' + str(jvm.get('idx')) + '.seq6.str')" 143 | value: "" 144 | content: "jvm.eval('AI-related story: ' + jvm.get('article_content_' + str(jvm.get('idx')) + '.seq3.str'))" 145 | else: [] 146 | end_seq: 6 147 | overall_outcome: "The content of the top stories on Hacker News have been fetched and decisions have been made on whether to post them to Slack. The messages prepared to be posted to Slack can be retrieved with keys like 'slack_message_.seq6.str'" 148 | ``` 149 | """ 150 | -------------------------------------------------------------------------------- /data/prompts/reviser_user: -------------------------------------------------------------------------------- 1 | Here are the instructions you will revise: 2 | ```yaml 3 | {instructions} 4 | ``` 5 | {review_feedback} 6 | 7 | Please give me the revised instructions: 8 | ```yaml 9 | -------------------------------------------------------------------------------- /data/prompts/text_completion_sys: -------------------------------------------------------------------------------- 1 | As an AI language model, your task is to process the user's request based on the input content and respond in JSON format as per the given output template. 2 | 3 | An example of output template: 4 | ```json 5 | { 6 | "kvs": [ 7 | { 8 | "key": "blog_content.seq3.str", 9 | "value": 10 | }, 11 | ... 12 | ] 13 | } 14 | ``` 15 | 16 | The keys in the output template follow this pattern: '.seq.'. In this pattern, 'X' remains constant, and '' represents Python's data types, including {int, bool, str, list}. 'list' represents a list of strings or integers, 'int' stands for an integer, and 'str' represents a string. 17 | 18 | The term '' in the output template appears in place of the values that you need to provide. 19 | -------------------------------------------------------------------------------- /data/prompts/text_completion_user: -------------------------------------------------------------------------------- 1 | User's Request: {request} 2 | 3 | The Input Content: 4 | """ 5 | {content} 6 | """ 7 | 8 | The Output Template: 9 | ```json 10 | {output_format} 11 | ``` 12 | 13 | Your response: 14 | ```json 15 | -------------------------------------------------------------------------------- /data/prompts/translator_sys: -------------------------------------------------------------------------------- 1 | As Jarvis, an AI model with the role of translating task into JVM(AKA Jarvis virtual machine)'s instructions. 2 | You will fully leverage user's hints(if any), reuse them to generate instructions efficiently. 3 | 4 | You will define milestones for the task, and then generate instructions for each milestone. 5 | 6 | When handling data, bear in mind that dynamic keys are critical to the operation of this AI. Dynamic keys provide the flexibility to manipulate and access data. They can cater to the specific needs of a variety of tasks. 7 | Dynamic keys, must be the format: '_.seq.', where 'X' could vary based on context, 'idx' is related to the loop index(can be optional if current instruction is not inside a loop), 'type' is the type of the value(which is one of Python's type: {int, str, list}) allow the AI to structure and access data in a flexible, non-static way. 8 | Dynamic keys are particularly useful in loop structures, where data is iteratively processed or collected. They allow the AI to dynamically create and access individual data entries, thus providing a more granular control over data. Be sure to construct and utilize dynamic keys wisely, allowing for efficient data manipulation and access across various tasks. 9 | 10 | ## JVM Instructions 11 | ### Basic Instructions: 12 | - 'WebSearch': Returns a list of URLs from a web search engine based on the provided query. 13 | - 'FetchWebContent': Fetches the content of a specified URL, specifically designed for web pages, and extracts plain text from HTML forms. 14 | - 'TextCompletion': Leverages the AI's capabilities to generate content, complete text, translate code, consolidate content, create summary, or extract information from provided text in an interactive and user-friendly manner. 15 | 16 | ### Advanced Instructions: 17 | - 'If': Acts as a conditional control structure within the JVM. It evaluates a condition and executes a set of instructions based on whether the condition is true or false. 18 | - 'Loop': Used to repeat a certain set of instructions for a specified number of iterations. 19 | - 'RunPython': Executes Python code. This instruction is used for performing I/O, calling API. 20 | 21 | ### Arguments for JVM instructions: 22 | Common arguments for each instruction: 23 | - objective: The string contains an objective description for this instruction only. 24 | - inside_loop: Whether this instruction is inside a loop or not. 25 | 26 | 1. 'WebSearch': { 27 | "query": The search query string. 28 | "save_to": The dynamic key('type' is always 'list') under which the URLs of search result should be stored in the database. 29 | } 30 | 31 | 2. 'FetchWebContent': { 32 | "url": The URL from which content should be fetched. This URL must be a web page URL, local file paths or non-web URLs are not supported. 33 | "save_to": This argument specifies the dynamic key under which the fetched results will be stored in the database. If inside a loop, ensure the dynamic key follows the "" format to guarantee its uniqueness. 34 | } 35 | 36 | 3. 'TextCompletion': { 37 | "request": A narrative that describes what TextCompletion needs to do. It includes the objective of the task (e.g., what needs to be done with the input data). 38 | "output_format": The output_format must be described what to save by using the json template: {'kvs': [{'key': '.seq.', 'value': ''}, ...]}, and use dynamic key with if inside a loop, e.g. {'kvs': [{'key': '_.seq.', 'value': ''}, ...]}. 39 | "content": This is the content to be processed. It's the raw input that the AI will work on. 40 | } 41 | 42 | 3. 'If': { 43 | "condition": The condition to be evaluated. 44 | "then": The list of instructions to be executed if the condition is true. 45 | "else": The list of instructions to be executed if the condition is false. 46 | } 47 | 48 | 4. 'Loop': { 49 | "count": The number of iterations for the loop, can be evaluated dynamically by using the lazy eval syntax. Example: "jvm.eval(len(jvm.get('fetched_urls.seq3.list')))" 50 | "idx": jvm.eval(jvm.get('idx')). The number of iterations is determined by the 'count' argument, the initial value of 'idx' can be retrieved with jvm.eval(jvm.get('idx')), the initial value of jvm.get('idx') is 0. For each iteration, the AI checks the jvm.get('idx') argument. Based on these values, the AI will repeat the specific instructions found in the 'instructions' field. jvm.get('idx') is an sys variable that keeps track of the current loop iteration. If you want to print current search result on the current loop iteration, you can use the following code: ```python print(jvm.get('search_results.seq1.list')[jvm.get('idx')])```. here is another example to construct a dynamic key for any instructions inside the loop, code: ```python jvm.set('relevant_info_' + str(jvm.get('idx')) + '.seq3'), value)```, assume the value 'count' of loop is 3, the constructed key will be evaluated as: 'relevant_info_0.seq3', 'relevant_info_1.seq3', 'relevant_info_2.seq3', so we can use 'relevant_info_' as prefix to list all the keys with the prefix 'relevant_info_' by using jvm.list_keys_with_prefix('relevant_info_'), or we can use jvm.list_values_with_key_prefix('relevant_info_') to get all the values with the prefix 'relevant_info_'. 51 | "instructions": The list of instructions to be repeated for each iteration. 52 | } 53 | 54 | 5. 'RunPython': { // do not use any non-existing arguments 55 | "code": A multiline string containing the entire Python code to be executed. Inside the code, you can call JVM's functions directly without using jvm.eval() syntax to access and manipulate data, such as ```python jvm.set("temperature.seq3.int", 67)```, jvm.get() and so on, because jvm module is imported by default. 56 | "code_review": does it achieve the objective? Which part does not follow the coding standards? 57 | "pkg_dependencies": A list of Python packages that the code depends on. You should specify only the root package names and not the individual modules or submodules inside them. For example, if you're using the relativedelta class from the dateutil library, you only need to mention dateutil and not dateutil.relativedelta. Standard Python modules like json, os, sys, etc., should be excluded from this list. Before specifying any package, ensure that it's a valid package name that can be installed via pip. 58 | } 59 | - Coding Standards: 60 | - Include comments to explain functionality and decision-making processes. 61 | - Avoid placeholder code. 62 | - Avoid use f-strings. 63 | - Avoid call functions that do not exist. 64 | 65 | Everything inside output_format argument of a instruction will be evaluated and persist to database. No further persist/save action is required. 66 | 67 | 68 | 69 | ## Instruction Sequence 70 | Each instruction is given a unique, incrementing identifier called 'seq'. The sequence starts from a user-defined value, 'start_seq'. This sequence number helps to keep track of the order of the instructions. 71 | 72 | 73 | ## JVM functions that operate on database 74 | Use these functions to manipulate data in JVM(key name must has a seq as suffix to indicate the source of the data): 75 | key-value API is the only way to pass information between tasks. The database can be accessed by the following methods: 76 | 77 | - jvm.get(''): returns an object of the specified key 78 | - jvm.set('', ): sets an object to the specified key 79 | - jvm.list_values_with_key_prefix(''): returns a list of object with the specified prefix, it's very efficient to get all the values with the same prefix. Usually work with Loop instruction together. 80 | - jvm.list_keys_with_prefix(''): returns a list of key:string with the specified prefix, it's very efficient to get all the keys with the same prefix. Usually work with Loop instruction together. 81 | 82 | 83 | ## Output Requirements 84 | Your output MUST have these fields: task, thoughts, hints_from_user, end_seq(indicates the maximum instruction sequence number), instructions, overall_outcome. 85 | 86 | When forming the 'overall_outcome', explain the overall outcome we had after succeeded, what is the final result and how to retrieve the results (specify a correct key name or (both key prefix and postfix if the key can't be retrieved by jvm.get) ), As there are other tasks will use the result, give hints to next task. 87 | 88 | Remember, your task is to generate instructions that will run on JVM based on these guidelines, Don't generate non-exist instructions. 89 | -------------------------------------------------------------------------------- /data/prompts/translator_user: -------------------------------------------------------------------------------- 1 | Please use the provided YAML template in your response. Follow these requirements: 2 | 1. Leverage the outcomes from the user's hints to meet the objective. 3 | 4 | ```yaml 5 | task: {task} 6 | objective: {objective} 7 | thoughts: 8 | hints_from_user: {hints} 9 | start_seq: {start_seq} 10 | instructions: 11 | end_seq: 12 | overall_outcome: 13 | ``` 14 | 15 | Your Response: 16 | ```yaml 17 | -------------------------------------------------------------------------------- /data/prompts/translator_user_w_user_result: -------------------------------------------------------------------------------- 1 | Please use the provided YAML template in your response. Follow these requirements: 2 | 1. Leverage the outcomes from the user's hints to meet the objective. 3 | 2. Create an instruction to produce the final result that the user will see. 4 | * The result should include specific details and data essential for directly addressing the task. For instance, instead of just saying 'The search has been completed. The results will be processed in the next tasks.', provide metrics or evidence, such as 'Here are some resources on TiDB Serverless: https://www.pingcap.com/tidb-serverless/, ...'. 5 | * Do not use any jvm keys, users can't see jvm data. 6 | * Save this final result to key `task_{task_num}.output.str`. 7 | 3. When crafting the overall outcome which is mainly for transferring data between tasks, please exclude the instruction for the user-facing result and do not reference its key `task_.output.str`. 8 | 9 | ```yaml 10 | task: {task} 11 | objective: {objective} 12 | thoughts: 13 | hints_from_user: {hints} 14 | start_seq: {start_seq} 15 | instructions: 16 | end_seq: 17 | overall_outcome: 18 | ``` 19 | 20 | Your Response: 21 | ```yaml 22 | -------------------------------------------------------------------------------- /evaluator/customer_evaluator.py: -------------------------------------------------------------------------------- 1 | import re 2 | import yaml 3 | import openai 4 | from typing import Optional, Any, Dict 5 | 6 | from langsmith import RunEvaluator 7 | from langchain.evaluation.schema import StringEvaluator 8 | from langsmith.evaluation import EvaluationResult 9 | 10 | from langchain.chains import LLMChain 11 | 12 | from jarvis.smartgpt import gpt 13 | from jarvis.smartgpt import instruction 14 | 15 | 16 | class GrammarAccuracyEvaluator(StringEvaluator): 17 | def _check_jvm_syntax(self, prediction: str) -> bool: 18 | # Checking for valid jvm.eval and jvm.get calls 19 | eval_pattern = re.compile(r"jvm\.eval\([^)]+\)") 20 | get_pattern = re.compile(r"jvm\.get\([^)]+\)") 21 | 22 | if not all( 23 | [ 24 | bool(re.fullmatch(eval_pattern, s)) 25 | for s in re.findall(r"jvm\.eval\([^)]+\)", prediction) 26 | ] 27 | ): 28 | return False 29 | if not all( 30 | [ 31 | bool(re.fullmatch(get_pattern, s)) 32 | for s in re.findall(r"jvm\.get\([^)]+\)", prediction) 33 | ] 34 | ): 35 | return False 36 | 37 | # Add more checks for other syntax rules if needed 38 | return True 39 | 40 | def _evaluate_strings( 41 | self, 42 | *, 43 | prediction: str, 44 | reference: Optional[str] = None, 45 | input: Optional[str] = None, 46 | **kwargs: Any, 47 | ) -> Dict[str, Any]: 48 | if self._check_jvm_syntax(prediction): 49 | return { 50 | "score": 1.0, 51 | "value": prediction, 52 | "reasoning": "The prediction follows the correct JVM syntax.", 53 | } 54 | else: 55 | return { 56 | "score": 0.0, 57 | "value": prediction, 58 | "reasoning": "The prediction does not follow the correct JVM syntax.", 59 | } 60 | 61 | 62 | class YAMLCorrectnessEvaluator(StringEvaluator): 63 | def _evaluate_strings( 64 | self, 65 | *, 66 | prediction: str, 67 | reference: Optional[str] = None, 68 | input: Optional[str] = None, 69 | **kwargs: Any, 70 | ) -> Dict[str, Any]: 71 | try: 72 | yaml.safe_load(prediction) 73 | score = 1.0 74 | reasoning = "The prediction is a valid YAML format." 75 | except yaml.YAMLError as e: 76 | score = 0.0 77 | reasoning = str(e) 78 | return { 79 | "score": score, 80 | "value": prediction, 81 | "reasoning": reasoning, 82 | } 83 | 84 | 85 | class InstructionValidityEvaluator(RunEvaluator): 86 | LLM_TEMPLATE = """ 87 | Here are a set of instructions: 88 | -------- 89 | {instructions} 90 | -------- 91 | On a scale from 0 to 100, how valid and complete do these instructions appear? Provide a score at the end. 92 | """ 93 | 94 | def __init__(self): 95 | self.llm = self._initialize_llm() 96 | 97 | @staticmethod 98 | def _initialize_llm(): 99 | """Initialize the ChatOpenAI based on the API type.""" 100 | return gpt.OPEN_AI_MODELS_HUB[gpt.GPT_4].get_llm() 101 | 102 | def evaluate_run(self, run, example: Optional[dict] = None) -> EvaluationResult: 103 | output_string = run.outputs.get("output") 104 | output_content = yaml.safe_load(output_string) 105 | task = output_content.get("task") 106 | 107 | instructions_list = self._get_instructions_from_output(output_content) 108 | if not instructions_list: 109 | return EvaluationResult( 110 | key="InstructionValidity", score=0, details="No instructions found" 111 | ) 112 | 113 | return self._execute_and_evaluate_instructions(instructions_list, task) 114 | 115 | @staticmethod 116 | def _get_instructions_from_output(output_content): 117 | """Extract instructions if they exist and are non-empty.""" 118 | instructions = output_content.get("instructions") 119 | return instructions if isinstance(instructions, list) and instructions else None 120 | 121 | def _execute_and_evaluate_instructions(self, instructions, task): 122 | try: 123 | interpreter = instruction.JVMInterpreter() 124 | interpreter.run(instructions, task=task) 125 | execution_result = interpreter.get_results() 126 | except Exception as e: 127 | return EvaluationResult(key="InstructionValidity", score=0, details=str(e)) 128 | 129 | return self._evaluate_execution_result(execution_result) 130 | 131 | def _evaluate_execution_result(self, execution_result): 132 | evaluator_result = LLMChain.from_string( 133 | llm=self.llm, template=self.LLM_TEMPLATE 134 | )(dict(instructions=execution_result)) 135 | score = self._extract_score_from_evaluator_result(evaluator_result) 136 | return EvaluationResult(key="InstructionValidity", score=score) 137 | 138 | @staticmethod 139 | def _extract_score_from_evaluator_result(evaluator_result): 140 | """Extract score from the AI's evaluator result.""" 141 | score_match = re.search(r"\d+", evaluator_result["text"]) 142 | return float(score_match.group(0).strip()) / 100.0 if score_match else 0 143 | -------------------------------------------------------------------------------- /evaluator/evaluate_translator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any, Dict, List, Optional 3 | import openai 4 | import os 5 | 6 | from langsmith import Client 7 | from langchain.smith import RunEvalConfig, run_on_dataset 8 | 9 | from evaluator.customer_evaluator import ( 10 | GrammarAccuracyEvaluator, 11 | YAMLCorrectnessEvaluator, 12 | InstructionValidityEvaluator, 13 | ) 14 | from jarvis.smartgpt.translator import Translator 15 | from jarvis.smartgpt import gpt 16 | from jarvis.smartgpt import initializer 17 | from langchain.prompts.prompt import PromptTemplate 18 | 19 | from langchain.callbacks.manager import ( 20 | AsyncCallbackManagerForChainRun, 21 | CallbackManagerForChainRun, 22 | ) 23 | from langchain.chat_models import ChatOpenAI 24 | from langchain.chains.base import Chain 25 | 26 | initializer.setup() 27 | workspace_dir = "workspace/evaluation" 28 | os.makedirs(workspace_dir, exist_ok=True) 29 | os.chdir(workspace_dir) 30 | 31 | 32 | class TranslatorMockChain(Chain): 33 | """ 34 | An example of a custom chain. 35 | """ 36 | 37 | input_key: str = "task_info" #: :meta private: 38 | output_key: str = "output" #: :meta private: 39 | jarvis_translator = Translator(gpt.GPT_4) 40 | 41 | class Config: 42 | extra = "forbid" 43 | arbitrary_types_allowed = True 44 | 45 | @property 46 | def input_keys(self) -> List[str]: 47 | return [self.input_key] 48 | 49 | @property 50 | def output_keys(self) -> List[str]: 51 | return [self.output_key] 52 | 53 | def _call( 54 | self, 55 | inputs: Dict[str, Any], 56 | run_manager: Optional[CallbackManagerForChainRun] = None, 57 | ) -> Dict[str, str]: 58 | result = self.jarvis_translator.translate_to_instructions(**inputs) 59 | return {self.output_key: result} 60 | 61 | async def _acall( 62 | self, 63 | inputs: Dict[str, Any], 64 | run_manager: Optional[AsyncCallbackManagerForChainRun] = None, 65 | ) -> Dict[str, str]: 66 | result = self.jarvis_translator.translate_to_instructions(**inputs) 67 | return {self.output_key: result} 68 | 69 | @property 70 | def _chain_type(self) -> str: 71 | return "my_custom_chain" 72 | 73 | 74 | if __name__ == "__main__": 75 | cot_template = """You are a Computer Science instructor specializing in the Java Virtual Machine (JVM) curriculum. With a deep understanding of JVM syntax and operational mechanics, you are now grading a quiz focusing on custom JVM syntax. 76 | You will be provided with a question, the student's response, and the correct answer. Your task is to evaluate the student's answer based on the true JVM syntax and determine if it is CORRECT or INCORRECT, based on the context. 77 | Write out in a step by step manner your reasoning to be sure that your conclusion is correct. Avoid simply stating the correct answer at the outset. 78 | 79 | Some JVM syntax you need to remember: 80 | - jvm.eval(expression): Evaluates the given expression and returns its value. This is typically used to dynamically determine arguments for other JVM instructions, especially when such arguments depend on the current state of the database or the results of previous instructions. For instance, it can be used inside the 'Loop' instruction's 'count' argument to determine the number of iterations based on a list's length stored in the database. 81 | - jvm.get('key_name'): returns an object of the specified key 82 | - jvm.set('key_name', value): sets an object to the specified key 83 | - jvm.list_values_with_key_prefix('prefix'): returns a list of object with the specified prefix, it's very efficient to get all the values with the same prefix. Usually work with Loop instruction together. 84 | - jvm.list_keys_with_prefix('prefix'): returns a list of key:string with the specified prefix, it's very efficient to get all the keys with the same prefix. Usually work with Loop instruction together. 85 | - In the JVM system, the suffix of a key determines the type of its value. For instance, keys ending in .list indicate that the value is a python list, while keys ending in .str signify that the value is a string. Always refer to the key's suffix to understand the expected data type. 86 | 87 | And some instructions you need to remember: 88 | 1. The '' in instructions is a placeholder that will be replaced during execution. 89 | 2. The types of instructions are in the range: 'WebSearch', 'FetchWebContent', 'TextCompletion', 'If', 'Loop', and 'RunPython'. And the arguments for each instruction are pre-defined. 90 | 3. Remember that the value stored under the 'idx' key is always a number. 91 | 4. Note: jvm.eval serves as a specialized marker, identifying portions of an expression requiring evaluation. Within any particular expression, there should only be a single segment demanding evaluation. All occurrences of jvm.get('') must be nested within the scope of a jvm.eval. 92 | 5. !IMPORTANT!: Use the syntax from the provided correct answers in the CONTEXT section as a reference for accurate syntax usage. However, different implementations (different instructions, key name, seq number, overall outcome) that still produce correct results are acceptable, so please don't compare implementations too much, minor deviations and issues can be overlooked if the student's answer achieves the same functionality or result. 93 | 94 | Example Format: 95 | QUESTION: question here 96 | CONTEXT: context the question is about here 97 | STUDENT ANSWER: student's answer here 98 | EXPLANATION: step by step reasoning here 99 | GRADE: CORRECT or INCORRECT here 100 | 101 | Evaluation Guide: 102 | 1. Syntactical Accuracy: Primarily evaluate student answers based on their syntactical accuracy. Minor deviations and issues can be overlooked if the student's answer achieves the same functionality or result. 103 | 2. Correctness of YAML: Assess the correctness of the YAML used in the student's answer. This is an important aspect to consider during the evaluation. 104 | 3. Effectiveness of Instructions: Evaluate whether the instructions provided in the student's answer effectively achieve the desired task. The practical application and outcome are pivotal. 105 | While all points are important, always prioritize correctness in syntax above all. 106 | 107 | Begin! 108 | 109 | QUESTION: {query} 110 | CONTEXT: {context} 111 | STUDENT ANSWER: {result} 112 | EXPLANATION:""" 113 | 114 | COT_PROMPT = PromptTemplate( 115 | input_variables=["query", "context", "result"], template=cot_template 116 | ) 117 | 118 | llm = gpt.OPEN_AI_MODELS_HUB[gpt.GPT_4].get_llm() 119 | 120 | client = Client() 121 | dataset_name = "Jarvis translator" 122 | 123 | eval_config = RunEvalConfig( 124 | evaluators=[ 125 | RunEvalConfig.CoTQA(prompt=COT_PROMPT), 126 | ], 127 | custom_evaluators=[ 128 | GrammarAccuracyEvaluator(), 129 | YAMLCorrectnessEvaluator(), 130 | InstructionValidityEvaluator(), 131 | ], 132 | eval_llm=llm, 133 | ) 134 | 135 | def input_mapper(input: Dict[str, Any]): 136 | return {"task_info": input} 137 | 138 | chain_results = run_on_dataset( 139 | client=client, 140 | dataset_name=dataset_name, 141 | llm_or_chain_factory=lambda: TranslatorMockChain(), 142 | concurrency_level=1, 143 | evaluation=eval_config, 144 | num_repetitions=2, 145 | verbose=True, 146 | input_mapper=input_mapper, 147 | ) 148 | -------------------------------------------------------------------------------- /example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import grpc\n", 10 | "import jarvis.server.jarvis_pb2 as jarvis_pb2\n", 11 | "import jarvis.server.jarvis_pb2_grpc as jarvis_pb2_grpc\n", 12 | "\n", 13 | "channel = grpc.insecure_channel(\"localhost:51155\")\n", 14 | "stub = jarvis_pb2_grpc.JarvisStub(channel)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "response=stub.Execute(\n", 24 | " jarvis_pb2.ExecuteRequest(\n", 25 | " task=(\n", 26 | " \"Collect the top three articles featured on Hacker News (https://news.ycombinator.com/), \"\n", 27 | " \"and produce a single professional reading summary that encompasses the content of all three articles, formatted in a user-friendly manner.\"\n", 28 | " )\n", 29 | " )\n", 30 | ")\n", 31 | "response" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "stub.SaveSkill(\n", 41 | " jarvis_pb2.SaveSkillRequest(\n", 42 | " executor_id=response.executor_id,\n", 43 | " skill_name=\"\",\n", 44 | " )\n", 45 | ")" 46 | ] 47 | } 48 | ], 49 | "metadata": { 50 | "kernelspec": { 51 | "display_name": "jarvis", 52 | "language": "python", 53 | "name": "python3" 54 | }, 55 | "language_info": { 56 | "codemirror_mode": { 57 | "name": "ipython", 58 | "version": 3 59 | }, 60 | "file_extension": ".py", 61 | "mimetype": "text/x-python", 62 | "name": "python", 63 | "nbconvert_exporter": "python", 64 | "pygments_lexer": "ipython3", 65 | "version": "3.10.11" 66 | } 67 | }, 68 | "nbformat": 4, 69 | "nbformat_minor": 2 70 | } 71 | -------------------------------------------------------------------------------- /experiments/autogen_jarvis.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import glob 4 | from typing import Dict, Optional, Union, List, Any, Tuple 5 | 6 | from autogen import ConversableAgent 7 | from autogen import UserProxyAgent 8 | from autogen import Agent 9 | 10 | from jarvis.agent.jarvis_agent import JarvisAgent, EMPTY_FIELD_INDICATOR 11 | 12 | def clear_files_in_directory(directory_path): 13 | file_patterns = ["*.yaml", "*.json", "*.txt"] 14 | for pattern in file_patterns: 15 | files_to_delete = glob.glob(os.path.join(directory_path, pattern)) 16 | for file_path in files_to_delete: 17 | try: 18 | os.remove(file_path) 19 | logging.info(f"Remove file: {file_path}") 20 | except Exception as e: 21 | logging.info(f"Failed to remove {file_path}: {e}") 22 | 23 | 24 | class JarvisExecutor(ConversableAgent): 25 | def __init__(self, skill_lib_dir="skill_library", execution_dir="autogen_execution"): 26 | super().__init__( 27 | name="jarvis executor", 28 | system_message="Jarvis executes your task.", 29 | is_termination_msg=lambda x: x.get("content") == "TERMINATE", 30 | human_input_mode="NEVER", 31 | ) 32 | 33 | self.register_reply([Agent, None], JarvisExecutor.execute_task_and_replay) 34 | self.agent = JarvisAgent(skill_lib_dir) 35 | self.execution_dir = execution_dir 36 | 37 | 38 | def execute_task_and_replay( 39 | self, 40 | messages: Optional[List[Dict]] = None, 41 | sender: Optional[Agent] = None, 42 | config: Optional[Any] = None, 43 | ) -> Tuple[bool, Union[str, Dict, None]]: 44 | if messages is None or len(messages) == 0: 45 | return True, "Please provide a task description." 46 | 47 | task = messages[-1]["content"] 48 | 49 | try: 50 | response = self.agent.execute_with_skill_selection(self.execution_dir, task) 51 | except Exception as e: 52 | return True, str(e) 53 | 54 | if response.error: 55 | return True, response.error 56 | 57 | if response.result != EMPTY_FIELD_INDICATOR: 58 | return True, response.result 59 | 60 | return True, f"I had completed tasks {self.pretty_output(response)}." 61 | 62 | def pretty_output(self, exec_result): 63 | breaking1 = "-" * 50 64 | breaking2 = "=" * 50 65 | 66 | pretty_res = "" 67 | if exec_result.task_infos: 68 | pretty_res += f"\n{breaking2}\n" 69 | pretty_res += "Task Infos" 70 | pretty_res += f"\n{breaking2}\n\n" 71 | 72 | for task_info in exec_result.task_infos: 73 | pretty_res += f"Subtask: {task_info.task}\n" 74 | pretty_res += f"{breaking1}\n\n" 75 | 76 | return pretty_res 77 | 78 | if __name__ == "__main__": 79 | workspace_dir = "workspace/T9" 80 | os.makedirs(workspace_dir, exist_ok=True) 81 | os.chdir(workspace_dir) 82 | 83 | # Logging file name and line number 84 | logging.basicConfig( 85 | level=logging.INFO, 86 | format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s", 87 | filename="chain_jarvis.log", 88 | ) 89 | 90 | config_list = [ 91 | { 92 | 'model': 'gpt-4', 93 | 'api_key': os.getenv('OPENAI_API_KEY'), 94 | } 95 | ] 96 | jarvis = JarvisExecutor() 97 | user_proxy = UserProxyAgent( 98 | name="user_proxy", 99 | human_input_mode="ALWAYS", 100 | is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"), 101 | ) 102 | 103 | first_task = input("What can I assist you with today? Input: ") 104 | print() 105 | # the assistant receives a message from the user, which contains the task description 106 | user_proxy.initiate_chat(jarvis, message=first_task) 107 | -------------------------------------------------------------------------------- /jarvis/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Jarvis streamlines the process of task execution planning based on user-defined goals. Beyond that, it efficiently translates these tasks into JVM instructions, allowing seamless integration and execution. 4 | 5 | ## How to Use Jarvis 6 | 7 | ### Generating a Task Execution Plan 8 | 9 | For Direct Input Goals. Generate a plan for task execution directly from provided input goals: 10 | 11 | ``` 12 | python -m jarvis --replan 13 | ``` 14 | 15 | Using a Goal File. If you have predefined goals stored in a file, place this file in the 'workspace' directory. To generate a plan based on this file: 16 | 17 | ``` 18 | python -m jarvis --replan --goalfile= 19 | ``` 20 | 21 | ### Translating Tasks to JVM Instructions 22 | 23 | Translate All Tasks. Convert all tasks in the execution plan into their corresponding JVM instructions: 24 | 25 | ``` 26 | python -m jarvis 27 | ``` 28 | 29 | Translate Specific Task. If you wish to only translate a task with a particular task number: 30 | 31 | ``` 32 | python -m jarvis --compile= 33 | ``` 34 | 35 | ## Executing JVM Instructions 36 | 37 | Execute JVM instructions based on the specified task. Here are a couple of examples: 38 | 39 | ``` 40 | python -m jarvis --yaml=1.yaml 41 | python -m jarvis --yaml=2.yaml 42 | ``` -------------------------------------------------------------------------------- /jarvis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaut/jarvis/ac36633d5a966aa9cf20525b393638940675ccbd/jarvis/__init__.py -------------------------------------------------------------------------------- /jarvis/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import argparse 4 | import logging 5 | 6 | import yaml 7 | 8 | from jarvis.smartgpt import initializer 9 | from jarvis.smartgpt import gpt 10 | from jarvis.smartgpt import planner 11 | from jarvis.smartgpt import instruction 12 | from jarvis.smartgpt import compiler 13 | 14 | 15 | PLANNER_MODEL = gpt.GPT_4 16 | TRANSLATOR_MODEL = gpt.GPT_4 17 | 18 | def run(): 19 | # Initialize the Jarvis environment 20 | initializer.setup() 21 | 22 | # Parse command line arguments 23 | parser = argparse.ArgumentParser() 24 | # parser.add_argument('--config', type=str, default='config.yaml', help='Path to the configuration file') 25 | parser.add_argument('--replan', action='store_true', help='Create a new plan') 26 | parser.add_argument('--yaml', type=str, help='Path to the yaml file to execute plan from') 27 | parser.add_argument('--goalfile', type=str, default='', help='Specify the goal description file for Jarvis') 28 | parser.add_argument('--compile', type=int, default=0, help='Translate plan into instructions with given task number') 29 | parser.add_argument('--workspace', type=str, default='workspace', help='Specify the workspace directory') 30 | 31 | args = parser.parse_args() 32 | 33 | # Logging file name and line number 34 | logging.basicConfig( 35 | level=logging.INFO, 36 | format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s', 37 | stream=sys.stdout 38 | ) 39 | 40 | print("Welcome to Jarvis, your personal assistant for everyday tasks!\n") 41 | 42 | os.makedirs(args.workspace, exist_ok=True) 43 | os.chdir(args.workspace) 44 | 45 | if args.yaml: 46 | # Load the JVM instructions from the YAML file 47 | with open(args.yaml, 'r') as f: 48 | task_instrs = yaml.safe_load(f) 49 | logging.info(f"Running JVM Instructions:\n{task_instrs}") 50 | 51 | interpreter = instruction.JVMInterpreter() 52 | interpreter.run(task_instrs["instructions"], task=task_instrs["task"]) 53 | else: 54 | if args.replan: 55 | goal = "" 56 | if args.goalfile: 57 | if not os.path.isfile(args.goalfile): 58 | logging.error(f"The goal file {args.goalfile} does not exist") 59 | exit(1) 60 | with open(args.goalfile, 'r') as f: 61 | goal = f.read() 62 | logging.info("Regenerate plan ...") 63 | planner.gen_plan(PLANNER_MODEL, goal) 64 | elif args.compile: 65 | logging.info(f"Tranlate the given task[{args.compile}] into JVM instructions ...") 66 | compiler.Compiler(TRANSLATOR_MODEL).compile_task_in_plan(args.compile) 67 | else: 68 | logging.info("Tranlate all tasks in plan ...") 69 | compiler.Compiler(TRANSLATOR_MODEL).compile_plan() 70 | 71 | 72 | if __name__ == "__main__": 73 | run() 74 | -------------------------------------------------------------------------------- /jarvis/agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaut/jarvis/ac36633d5a966aa9cf20525b393638940675ccbd/jarvis/agent/__init__.py -------------------------------------------------------------------------------- /jarvis/agent/skill.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import logging 3 | import glob 4 | import os 5 | import yaml 6 | import json 7 | from typing import Dict, Optional 8 | import traceback 9 | 10 | from langchain.vectorstores import Chroma 11 | 12 | from jarvis.smartgpt import gpt 13 | 14 | skill_gen_prompt = """ 15 | You are a helpful assistant that writes a json format skill description for the given task and it's execution plan. 16 | 17 | You should only respond in JSON format as described below 18 | { 19 | "skill_name": "get_beijing_weather_of_the_day", 20 | "skill_description": "Get today's weather in Beijing by browsing the weather websites" 21 | } 22 | 23 | Ensure the response can be parsed by Python json.loads""" 24 | 25 | 26 | def custom_skill_copytree(src_dir, dst_dir): 27 | os.makedirs(dst_dir, exist_ok=True) 28 | 29 | for file_name in os.listdir(src_dir): 30 | s = os.path.join(src_dir, file_name) 31 | d = os.path.join(dst_dir, file_name) 32 | 33 | if os.path.isdir(s): 34 | continue # Skip directories 35 | 36 | if file_name.endswith(".yaml") or file_name.endswith(".txt"): 37 | shutil.copy2(s, d) 38 | 39 | 40 | class SkillManager: 41 | name = "skill_saver" 42 | description = "A skill that saves jarvis skill in a previous step within the skills folder. Not for writing code." 43 | 44 | def __init__( 45 | self, 46 | model_name=gpt.GPT_3_5_TURBO_16K, 47 | retrieval_top_k=5, 48 | skill_library_dir="skill_library", 49 | ): 50 | self.skill_library_dir = skill_library_dir 51 | self.retrieval_top_k = retrieval_top_k 52 | self.model_name = model_name 53 | self.skill_code_dir = f"{skill_library_dir}/code" 54 | self.skill_vectordb_dir = f"{skill_library_dir}/vectordb" 55 | self.skill_metadata = f"{skill_library_dir}/skills.json" 56 | 57 | os.makedirs(self.skill_code_dir, exist_ok=True) 58 | os.makedirs(self.skill_vectordb_dir, exist_ok=True) 59 | 60 | if os.path.exists(self.skill_metadata): 61 | logging.info(f"Loading Skill Manager from {self.skill_metadata}") 62 | with open(self.skill_metadata, "r") as file: 63 | self.skills = json.load(file) 64 | else: 65 | self.skills = {} 66 | 67 | embedding_func = gpt.OPEN_AI_MODELS_HUB["text-embedding-ada-002"] 68 | self.vectordb = Chroma( 69 | collection_name="skill_vectordb", 70 | embedding_function=embedding_func, 71 | persist_directory=self.skill_vectordb_dir, 72 | ) 73 | 74 | assert self.vectordb._collection.count() == len(self.skills), ( 75 | f"Skill Manager's vectordb is not synced with skills.json.\n" 76 | f"There are {self.vectordb._collection.count()} skills in vectordb but {len(self.skills)} skills in skills.json.\n" 77 | f"You may need to manually delete the vectordb directory for running from scratch." 78 | ) 79 | 80 | def clone_skill(self, skill_name, dest_dir): 81 | skill = self.skills.get(skill_name) 82 | if skill is None: 83 | raise ValueError(f"Skill '{skill_name}' not found.") 84 | 85 | try: 86 | custom_skill_copytree(self._skill_dir(skill["skill_name_w_ver"]), dest_dir) 87 | except Exception as e: 88 | logging.error("Error saving skill {resp}: {e}") 89 | logging.info(traceback.format_exc()) 90 | raise e 91 | 92 | return None 93 | 94 | def add_new_skill(self, task_dir, skill_name: Optional[str] = None): 95 | task, code = self.load_skill_from_dir(task_dir) 96 | if skill_name is None or len(skill_name) <= 3: 97 | # use gpt to generate skill name while skill_name is not provided or too short 98 | skill_name, _ = self.generate_skill_description(task, code) 99 | skill_description = task 100 | 101 | # prepare skill name and skill dir 102 | if skill_name in self.skills: 103 | logging.info(f"Skill {skill_name} already exists. Rewriting!") 104 | self.vectordb._collection.delete(ids=[skill_name]) 105 | i = 2 106 | while f"{skill_name}V{i}" in os.listdir(self.skill_code_dir): 107 | i += 1 108 | dumped_skill_name = f"{skill_name}V{i}" 109 | else: 110 | dumped_skill_name = skill_name 111 | 112 | skill_dir = self._skill_dir(dumped_skill_name) 113 | logging.info( 114 | f"generate skill.... skill_dir: {skill_dir}, skill_name: {skill_name}, skill_description: {skill_description}" 115 | ) 116 | 117 | # save skill 118 | try: 119 | custom_skill_copytree(task_dir, skill_dir) 120 | except Exception as e: 121 | logging.error("Error saving skill {resp}: {e}") 122 | logging.info(traceback.format_exc()) 123 | raise e 124 | 125 | self.vectordb.add_texts( 126 | texts=[skill_description], 127 | ids=[skill_name], 128 | metadatas=[{"skill_name": skill_name}], 129 | ) 130 | self.skills[skill_name] = { 131 | "skill_code": code, 132 | "skill_description": skill_description, 133 | "skill_name_w_ver": dumped_skill_name, 134 | } 135 | assert self.vectordb._collection.count() == len( 136 | self.skills 137 | ), "vectordb is not synced with skills.json" 138 | 139 | with open(self.skill_metadata, "w") as file: 140 | json.dump(self.skills, file) 141 | 142 | self.vectordb.persist() 143 | logging.info(f"Saving skill {skill_name} for {task} to {skill_dir}") 144 | return skill_name 145 | 146 | def generate_skill_description(self, task, code): 147 | sys_prompt = "Please review the task and its execution plan, and give the task a suitable name\n" 148 | user_prompt = f"Come up with a detail skill name (skill name should be function-name style, eg. 'get_weather'; skill name should detailed to be unqieu) for the task({task}) execution plan:\n{code}\n###\nSKILL_NAME:" 149 | skill_name = gpt.complete( 150 | prompt=user_prompt, model=self.model_name, system_prompt=sys_prompt 151 | ) 152 | return (skill_name, None) 153 | 154 | def retrieve_skills(self, query): 155 | k = min(self.vectordb._collection.count(), self.retrieval_top_k) 156 | if k == 0: 157 | return {} 158 | logging.info(f"Skill Manager retrieving for {k} skills") 159 | try: 160 | docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k) 161 | except Exception as e: 162 | logging.error(f"Error retrieving skills for {query}: {e}") 163 | return {} 164 | 165 | # Sort docs_and_scores by score in descending order 166 | logging.info( 167 | f"Skill Manager retrieved skills: " 168 | f"{', '.join([doc.metadata['skill_name'] for doc, _ in docs_and_scores])}" 169 | ) 170 | 171 | skills = {} 172 | for doc, score in docs_and_scores: 173 | logging.info( 174 | f"skill: {doc.metadata['skill_name']}, score: {score} for query: {query}" 175 | ) 176 | skills[doc.metadata["skill_name"]] = { 177 | "skill_description": self.skills[doc.metadata["skill_name"]][ 178 | "skill_description" 179 | ], 180 | "skill_code": self.skills[doc.metadata["skill_name"]]["skill_code"], 181 | "match_score": score, 182 | } 183 | 184 | return skills 185 | 186 | def load_skill_from_dir(self, task_dir): 187 | plan_file = os.path.join(task_dir, "plan.yaml") 188 | pwd = os.getcwd() 189 | logging.info(f"Loading skill from {pwd}/{task_dir}") 190 | if os.path.exists(plan_file) and os.path.isfile(plan_file): 191 | execution_plan, code = self.load_yaml(plan_file) 192 | plan = execution_plan.get("goal") 193 | if not plan: 194 | raise ValueError(f"plan not defined in {plan_file}") 195 | return (plan, code) 196 | 197 | task_files = glob.glob("[0-9]*.yaml", root_dir=task_dir) 198 | if len(task_files) != 1: 199 | raise ValueError( 200 | f"There should be exactly one task file under {task_dir}, but {task_files}" 201 | ) 202 | 203 | task_file = task_files[0] 204 | execution_plan, code = self.load_yaml(os.path.join(task_dir, task_file)) 205 | task = execution_plan.get("task") 206 | if not task: 207 | raise ValueError(f"task is not defined in {task_dir}/{task_file}") 208 | return (task, code) 209 | 210 | def load_yaml(self, file_name: str) -> (Dict, str): 211 | try: 212 | with open(file_name, "r") as stream: 213 | data = stream.read() 214 | return yaml.safe_load(data), data 215 | # return yaml.safe_load(stream), stream.read() 216 | except Exception as e: 217 | logging.error(f"Error loading file {file_name}: {e}") 218 | logging.info(traceback.format_exc()) 219 | raise 220 | 221 | def _skill_dir(self, skill_name): 222 | return os.path.join(self.skill_code_dir, skill_name) 223 | -------------------------------------------------------------------------------- /jarvis/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaut/jarvis/ac36633d5a966aa9cf20525b393638940675ccbd/jarvis/server/__init__.py -------------------------------------------------------------------------------- /jarvis/server/__main__.py: -------------------------------------------------------------------------------- 1 | import jarvis.server.jarvis_server as jarvis_server 2 | 3 | if __name__ == "__main__": 4 | jarvis_server.serve() 5 | -------------------------------------------------------------------------------- /jarvis/server/jarvis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package server; 4 | 5 | // The ExecuteRequest message represents the parameters of the execute function. 6 | message ExecuteRequest { 7 | // Executor id is the representative of the current executor. If executor_id are not provided, the task or goal will be assigned to a new executor. 8 | string executor_id = 1; 9 | // Goal is the overall goal of the task. It is used to generate hints for the. 10 | string goal = 2; 11 | // Task id is the task num representative of the current task. If not provided, the task number will be automatically generated. 12 | int32 task_id = 3; 13 | string task = 4; 14 | // dependent_tasks are the tasks that the current task depends on. If not provided, the task will be treated as a standalone task; if it is provided and executor_id is not provided, an error will be returned. 15 | repeated int32 dependent_tasks = 5; 16 | bool skip_gen = 6; 17 | bool enable_skill_library = 7; 18 | } 19 | 20 | // The ExecuteResponse message represents the result of the execute function. 21 | message ExecuteResponse { 22 | string executor_id = 1; 23 | string goal = 2; 24 | int32 task_id = 3; 25 | string task = 4; 26 | string result = 5; 27 | string error = 6; 28 | repeated ExecuteResponse subtasks = 7; 29 | } 30 | 31 | // The SaveSkillRequest message represents the parameters of the save skill function. 32 | message SaveSkillRequest { 33 | string executor_id = 1; 34 | string skill_name = 2; 35 | } 36 | 37 | // The SaveSkillResponse message represents the result of the save skill function. 38 | message SaveSkillResponse { 39 | string executor_id = 1; 40 | string result = 2; 41 | string error = 3; 42 | } 43 | 44 | // The Jarvis service provides an execute RPC method. 45 | service Jarvis { 46 | rpc Execute(ExecuteRequest) returns (ExecuteResponse); 47 | rpc ExecutePlan(ExecuteRequest) returns (ExecuteResponse); 48 | rpc SaveSkill(SaveSkillRequest) returns (SaveSkillResponse); 49 | } -------------------------------------------------------------------------------- /jarvis/server/jarvis_client.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | import json 3 | import time 4 | import jarvis.server.jarvis_pb2 as jarvis_pb2 5 | import jarvis.server.jarvis_pb2_grpc as jarvis_pb2_grpc 6 | from google.protobuf.json_format import MessageToJson 7 | 8 | 9 | simple_tweet_extraction = """Search tweets about TiDB on Twitter in the past day, and save these tweets data into TiDB cloud tables for furture analysis. 10 | * "TiDB Cloud", "TiDB", "PingCAP", "tidbcloud" are some useful tweet search query keywords, and exclude the retweet e.g. query='(tidb OR pingcap OR TiDB Cloud OR tidbcloud) -is:retweet'. 11 | * These tweets' topic should be "TiDB" in the tidb cloud tables. 12 | * The bearer token should be fetched from the environment variable TWEET_BEARER_TOKEN. 13 | * The max_results query parameter value should be 10. 14 | """ 15 | 16 | tweet_extraction = """Provide tweets about TiDB on Twitter in the past day, and insert these tweets data into TiDB cloud tables. 17 | * "TiDB Cloud", "TiDB", "PingCAP", "tidbcloud" are some useful tweet search query keywords, and exclude the retweet e.g. query='(tidb OR pingcap OR TiDB Cloud OR tidbcloud) -is:retweet'. 18 | * These tweets' topic should be "TiDB" in the tidb cloud tables. 19 | * The bearer token should be fetched from the environment variable TWEET_BEARER_TOKEN. 20 | * The start_time query parameter value should be an RFC3339 date-time for the past day, formatted correctly as 'YYYY-MM-DDTHH:MM:SSZ'. 21 | * The max_results query parameter value should be 100. 22 | * In addition to the above requirements, please make sure to fetch the following tweet fields when making the request: 'public_metrics', "id", "created_at", "referenced_tweets". Ensure the correct format and presence of all required fields in the response. 23 | * Please avoid the error (1062, "Duplicate entry '?' for key 'tweets.PRIMARY'") while inserting the tweets data into tidb cloud tables. 24 | * Please checkout the http response status, raise an error if the status is not 200. 25 | * Finally here a simple specification that you need 26 | # Response schema for https://api.twitter.com/2/tweets/search/recent 27 | { 28 | "type": "object", 29 | "properties": { 30 | "data": { 31 | "type": "array", 32 | "items": { 33 | "type": "object", 34 | "properties": { 35 | "public_metrics": { 36 | "type": "object", 37 | "properties": { 38 | "retweet_count": {"type": "integer"}, 39 | "reply_count": {"type": "integer"}, 40 | "like_count": {"type": "integer"}, 41 | "quote_count": {"type": "integer"}, 42 | "bookmark_count": {"type": "integer"}, 43 | "impression_count": {"type": "integer"} 44 | }, 45 | "required": ["retweet_count", "reply_count", "like_count", "quote_count", "bookmark_count", "impression_count"] 46 | }, 47 | "id": { 48 | "type": "string" 49 | }, 50 | "referenced_tweets": { 51 | "type": "array", 52 | "items": { 53 | "type": "object", 54 | "properties": { 55 | "type": {"type": "string"}, 56 | "id": {"type": "string"} 57 | }, 58 | "required": ["type", "id"] 59 | } 60 | }, 61 | "created_at": { 62 | "type": "string", 63 | "format": "date-time" 64 | }, 65 | "text": { 66 | "type": "string" 67 | } 68 | }, 69 | "required": ["public_metrics", "id", "created_at", "text"] 70 | } 71 | } 72 | }, 73 | "required": ["data"] 74 | } 75 | 76 | # Table schema in TiDB Cloud Database (mysql protocol) 77 | 78 | use jarvis_store; 79 | 80 | CREATE TABLE tweets ( 81 | id BIGINT PRIMARY KEY, 82 | created_at DATETIME, 83 | topic VARCHAR(64), 84 | text TEXT 85 | ); 86 | 87 | CREATE TABLE tweets_public_metrics ( 88 | id INT AUTO_INCREMENT PRIMARY KEY, 89 | tweet_id BIGINT UNIQUE KEY, 90 | retweet_count INT, 91 | reply_count INT, 92 | like_count INT, 93 | quote_count INT, 94 | bookmark_count INT, 95 | impression_count INT, 96 | FOREIGN KEY (tweet_id) REFERENCES tweets(id) 97 | ); 98 | 99 | CREATE TABLE referenced_tweets ( 100 | id INT AUTO_INCREMENT PRIMARY KEY, 101 | tweet_id BIGINT UNIQUE KEY, 102 | referenced_type VARCHAR(255), 103 | referenced_tweet_id BIGINT, 104 | FOREIGN KEY (tweet_id) REFERENCES tweets(id) 105 | ); 106 | 107 | # How to connect to TiDB Cloud Database (mysql protocol) 108 | 109 | import pymysql 110 | import os 111 | 112 | def connect_to_db(): 113 | \"\"\" 114 | example: 115 | # Create a connection to the TiDB Cloud database 116 | connection = connect_to_mysql_db() 117 | 118 | # Check if the connection was successful 119 | try: 120 | with connection.cursor() as cursor: 121 | ... 122 | finally: 123 | connection.close() 124 | \"\"\" 125 | 126 | user = os.getenv("TIDB_USER") 127 | password = os.getenv("TIDB_PASSWORD") 128 | host = os.getenv("TIDB_HOST") 129 | port = os.getenv("TIDB_PORT") or 4000 130 | database = os.getenv("TIDB_DATABASE", "jarvis_store") 131 | assert user is not None 132 | assert password is not None 133 | assert host is not None 134 | 135 | connection = pymysql.connect( 136 | host=host, 137 | user=user, 138 | port=int(port), 139 | password=password, 140 | database=database, 141 | ssl={'ca': '/etc/ssl/cert.pem'} 142 | ) 143 | 144 | return connection 145 | """ 146 | 147 | tweet_analysis = """write a twitter analysis report related to TiDB and CockroachDB to understand their presence and influence in social media. the Guides: 148 | 1. Load tweets, tweets_public_metrics, referenced_tweets into pandas dataframe for further analysis 149 | 2. Generate code to do the following analysis at least: 150 | * Tweet Volume Analysis: Compare the number of tweets for each product (TiDB and CockroachDB) over a defined period of time. 151 | * Impression Analysis: Calculate the total impressions for tweets related to each product and identify tweets that had the highest impression count. 152 | * Interaction Type Analysis: Analyze how many of the tweets for each product are original vs. replies or retweets. 153 | 3. Generate a analysis report file named `tidb_and_cockroach_tweets_analysis_report.md` using markdown format 154 | 155 | In adddition, the following are some QA that you need 156 | 157 | # how to store pandas dataframe into jvm, and load dataframe from jvm 158 | 159 | save dataframe: jvm.set("tweets_df.seq1.df", tweets_df.to_json()) 160 | get dataframe: tweets_df = pd.read_json(jvm.get("tweets_df.seq1.df")) 161 | 162 | # Table schema in TiDB Cloud Database (mysql protocol) 163 | 164 | use jarvis_store; 165 | 166 | CREATE TABLE tweets ( 167 | id BIGINT PRIMARY KEY, 168 | created_at DATETIME, 169 | topic VARCHAR(64), 170 | text TEXT 171 | ); 172 | 173 | CREATE TABLE tweets_public_metrics ( 174 | id INT AUTO_INCREMENT PRIMARY KEY, 175 | tweet_id BIGINT UNIQUE KEY, 176 | retweet_count INT, 177 | reply_count INT, 178 | like_count INT, 179 | quote_count INT, 180 | bookmark_count INT, 181 | impression_count INT, 182 | FOREIGN KEY (tweet_id) REFERENCES tweets(id) 183 | ); 184 | 185 | CREATE TABLE referenced_tweets ( 186 | id INT AUTO_INCREMENT PRIMARY KEY, 187 | tweet_id BIGINT UNIQUE KEY, 188 | referenced_type VARCHAR(255), 189 | referenced_tweet_id BIGINT, 190 | FOREIGN KEY (tweet_id) REFERENCES tweets(id) 191 | ); 192 | 193 | # How to distinguish TiDB and CockroachDB tweets 194 | 195 | TiDB tweets: tweets.topic='TiDB' 196 | CockroachDB tweets: tweets.topic='CockroachDB' 197 | 198 | # How to connect to TiDB Cloud Database (mysql protocol) 199 | 200 | import pymysql 201 | import os 202 | 203 | def connect_to_db(): 204 | \"\"\" 205 | example: 206 | # Create a connection to the TiDB Cloud database 207 | connection = connect_to_mysql_db() 208 | 209 | # Check if the connection was successful 210 | try: 211 | with connection.cursor() as cursor: 212 | ... 213 | finally: 214 | connection.close() 215 | \"\"\" 216 | 217 | user = os.getenv("TIDB_USER") 218 | password = os.getenv("TIDB_PASSWORD") 219 | host = os.getenv("TIDB_HOST") 220 | port = os.getenv("TIDB_PORT") or 4000 221 | database = os.getenv("TIDB_DATABASE", "jarvis_store") 222 | assert user is not None 223 | assert password is not None 224 | assert host is not None 225 | 226 | connection = pymysql.connect( 227 | host=host, 228 | user=user, 229 | port=int(port), 230 | password=password, 231 | database=database, 232 | ssl={'ca': '/etc/ssl/cert.pem'} 233 | ) 234 | 235 | return connection 236 | """ 237 | 238 | 239 | def train_skill(stub, task): 240 | response = stub.Execute( 241 | jarvis_pb2.ExecuteRequest( 242 | task=task, 243 | enable_skill_library=True, 244 | ) 245 | ) 246 | print(f"Jarvis client received: {response}") 247 | 248 | 249 | def save_skill(stub, executor_id, skill_name): 250 | response = stub.SaveSkill( 251 | jarvis_pb2.SaveSkillRequest( 252 | executor_id=executor_id, 253 | skill_name=skill_name, 254 | ) 255 | ) 256 | print(f"Jarvis client received: {MessageToJson(response)}") 257 | 258 | 259 | def replay(stub, executor_id): 260 | response = stub.Execute( 261 | jarvis_pb2.ExecuteRequest( 262 | task=f"replay {executor_id}", 263 | executor_id=executor_id, 264 | skip_gen=True, 265 | enable_skill_library=False, 266 | ) 267 | ) 268 | print(f"Jarvis client received: {MessageToJson(response)}") 269 | 270 | 271 | if __name__ == "__main__": 272 | channel = grpc.insecure_channel("localhost:51155") 273 | stub = jarvis_pb2_grpc.JarvisStub(channel) 274 | # replay(stub, "95ad26af69372155af7c92fa17c6b385") 275 | # save_skill(stub, "95ad26af69372155af7c92fa17c6b385", "load_tidb_tweets_of_past_days_into_tidbcloud") 276 | train_skill(stub, simple_tweet_extraction) 277 | -------------------------------------------------------------------------------- /jarvis/server/jarvis_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: jarvis.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import symbol_database as _symbol_database 8 | from google.protobuf.internal import builder as _builder 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cjarvis.proto\x12\x06server\"\x9b\x01\n\x0e\x45xecuteRequest\x12\x13\n\x0b\x65xecutor_id\x18\x01 \x01(\t\x12\x0c\n\x04goal\x18\x02 \x01(\t\x12\x0f\n\x07task_id\x18\x03 \x01(\x05\x12\x0c\n\x04task\x18\x04 \x01(\t\x12\x17\n\x0f\x64\x65pendent_tasks\x18\x05 \x03(\x05\x12\x10\n\x08skip_gen\x18\x06 \x01(\x08\x12\x1c\n\x14\x65nable_skill_library\x18\x07 \x01(\x08\"\x9d\x01\n\x0f\x45xecuteResponse\x12\x13\n\x0b\x65xecutor_id\x18\x01 \x01(\t\x12\x0c\n\x04goal\x18\x02 \x01(\t\x12\x0f\n\x07task_id\x18\x03 \x01(\x05\x12\x0c\n\x04task\x18\x04 \x01(\t\x12\x0e\n\x06result\x18\x05 \x01(\t\x12\r\n\x05\x65rror\x18\x06 \x01(\t\x12)\n\x08subtasks\x18\x07 \x03(\x0b\x32\x17.server.ExecuteResponse\";\n\x10SaveSkillRequest\x12\x13\n\x0b\x65xecutor_id\x18\x01 \x01(\t\x12\x12\n\nskill_name\x18\x02 \x01(\t\"G\n\x11SaveSkillResponse\x12\x13\n\x0b\x65xecutor_id\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t2\xc6\x01\n\x06Jarvis\x12:\n\x07\x45xecute\x12\x16.server.ExecuteRequest\x1a\x17.server.ExecuteResponse\x12>\n\x0b\x45xecutePlan\x12\x16.server.ExecuteRequest\x1a\x17.server.ExecuteResponse\x12@\n\tSaveSkill\x12\x18.server.SaveSkillRequest\x1a\x19.server.SaveSkillResponseb\x06proto3') 17 | 18 | _globals = globals() 19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'jarvis_pb2', _globals) 21 | if _descriptor._USE_C_DESCRIPTORS == False: 22 | 23 | DESCRIPTOR._options = None 24 | _globals['_EXECUTEREQUEST']._serialized_start=25 25 | _globals['_EXECUTEREQUEST']._serialized_end=180 26 | _globals['_EXECUTERESPONSE']._serialized_start=183 27 | _globals['_EXECUTERESPONSE']._serialized_end=340 28 | _globals['_SAVESKILLREQUEST']._serialized_start=342 29 | _globals['_SAVESKILLREQUEST']._serialized_end=401 30 | _globals['_SAVESKILLRESPONSE']._serialized_start=403 31 | _globals['_SAVESKILLRESPONSE']._serialized_end=474 32 | _globals['_JARVIS']._serialized_start=477 33 | _globals['_JARVIS']._serialized_end=675 34 | # @@protoc_insertion_point(module_scope) 35 | -------------------------------------------------------------------------------- /jarvis/server/jarvis_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import jarvis.server.jarvis_pb2 as jarvis__pb2 6 | 7 | class JarvisStub(object): 8 | """The Jarvis service provides an execute RPC method. 9 | """ 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.Execute = channel.unary_unary( 18 | '/server.Jarvis/Execute', 19 | request_serializer=jarvis__pb2.ExecuteRequest.SerializeToString, 20 | response_deserializer=jarvis__pb2.ExecuteResponse.FromString, 21 | ) 22 | self.ExecutePlan = channel.unary_unary( 23 | '/server.Jarvis/ExecutePlan', 24 | request_serializer=jarvis__pb2.ExecuteRequest.SerializeToString, 25 | response_deserializer=jarvis__pb2.ExecuteResponse.FromString, 26 | ) 27 | self.SaveSkill = channel.unary_unary( 28 | '/server.Jarvis/SaveSkill', 29 | request_serializer=jarvis__pb2.SaveSkillRequest.SerializeToString, 30 | response_deserializer=jarvis__pb2.SaveSkillResponse.FromString, 31 | ) 32 | 33 | 34 | class JarvisServicer(object): 35 | """The Jarvis service provides an execute RPC method. 36 | """ 37 | 38 | def Execute(self, request, context): 39 | """Missing associated documentation comment in .proto file.""" 40 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 41 | context.set_details('Method not implemented!') 42 | raise NotImplementedError('Method not implemented!') 43 | 44 | def ExecutePlan(self, request, context): 45 | """Missing associated documentation comment in .proto file.""" 46 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 47 | context.set_details('Method not implemented!') 48 | raise NotImplementedError('Method not implemented!') 49 | 50 | def SaveSkill(self, request, context): 51 | """Missing associated documentation comment in .proto file.""" 52 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 53 | context.set_details('Method not implemented!') 54 | raise NotImplementedError('Method not implemented!') 55 | 56 | 57 | def add_JarvisServicer_to_server(servicer, server): 58 | rpc_method_handlers = { 59 | 'Execute': grpc.unary_unary_rpc_method_handler( 60 | servicer.Execute, 61 | request_deserializer=jarvis__pb2.ExecuteRequest.FromString, 62 | response_serializer=jarvis__pb2.ExecuteResponse.SerializeToString, 63 | ), 64 | 'ExecutePlan': grpc.unary_unary_rpc_method_handler( 65 | servicer.ExecutePlan, 66 | request_deserializer=jarvis__pb2.ExecuteRequest.FromString, 67 | response_serializer=jarvis__pb2.ExecuteResponse.SerializeToString, 68 | ), 69 | 'SaveSkill': grpc.unary_unary_rpc_method_handler( 70 | servicer.SaveSkill, 71 | request_deserializer=jarvis__pb2.SaveSkillRequest.FromString, 72 | response_serializer=jarvis__pb2.SaveSkillResponse.SerializeToString, 73 | ), 74 | } 75 | generic_handler = grpc.method_handlers_generic_handler( 76 | 'server.Jarvis', rpc_method_handlers) 77 | server.add_generic_rpc_handlers((generic_handler,)) 78 | 79 | 80 | # This class is part of an EXPERIMENTAL API. 81 | class Jarvis(object): 82 | """The Jarvis service provides an execute RPC method. 83 | """ 84 | 85 | @staticmethod 86 | def Execute(request, 87 | target, 88 | options=(), 89 | channel_credentials=None, 90 | call_credentials=None, 91 | insecure=False, 92 | compression=None, 93 | wait_for_ready=None, 94 | timeout=None, 95 | metadata=None): 96 | return grpc.experimental.unary_unary(request, target, '/server.Jarvis/Execute', 97 | jarvis__pb2.ExecuteRequest.SerializeToString, 98 | jarvis__pb2.ExecuteResponse.FromString, 99 | options, channel_credentials, 100 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 101 | 102 | @staticmethod 103 | def ExecutePlan(request, 104 | target, 105 | options=(), 106 | channel_credentials=None, 107 | call_credentials=None, 108 | insecure=False, 109 | compression=None, 110 | wait_for_ready=None, 111 | timeout=None, 112 | metadata=None): 113 | return grpc.experimental.unary_unary(request, target, '/server.Jarvis/ExecutePlan', 114 | jarvis__pb2.ExecuteRequest.SerializeToString, 115 | jarvis__pb2.ExecuteResponse.FromString, 116 | options, channel_credentials, 117 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 118 | 119 | @staticmethod 120 | def SaveSkill(request, 121 | target, 122 | options=(), 123 | channel_credentials=None, 124 | call_credentials=None, 125 | insecure=False, 126 | compression=None, 127 | wait_for_ready=None, 128 | timeout=None, 129 | metadata=None): 130 | return grpc.experimental.unary_unary(request, target, '/server.Jarvis/SaveSkill', 131 | jarvis__pb2.SaveSkillRequest.SerializeToString, 132 | jarvis__pb2.SaveSkillResponse.FromString, 133 | options, channel_credentials, 134 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 135 | -------------------------------------------------------------------------------- /jarvis/server/jarvis_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | from datetime import datetime 5 | import hashlib 6 | from concurrent import futures 7 | import grpc 8 | from typing import Optional 9 | import traceback 10 | 11 | 12 | import jarvis.server.jarvis_pb2 as jarvis_pb2 13 | import jarvis.server.jarvis_pb2_grpc as jarvis_pb2_grpc 14 | from jarvis.agent.jarvis_agent import JarvisAgent, EMPTY_FIELD_INDICATOR 15 | 16 | 17 | class JarvisServicer(jarvis_pb2_grpc.JarvisServicer, JarvisAgent): 18 | def __init__(self, skill_library_dir: Optional[str] = None): 19 | self.agent = JarvisAgent(skill_library_dir) 20 | 21 | def Execute(self, request, context): 22 | # You can access the request parameters using request.task_id, request.task, etc. 23 | if len(request.task.strip()) <= 0: 24 | return jarvis_pb2.ExecuteResponse( 25 | executor_id=request.executor_id, 26 | task_id=request.task_id, 27 | error="task is not provided", 28 | ) 29 | task = request.task.strip() 30 | goal = "" 31 | if len(request.goal.strip()) > 0: 32 | goal = request.goal.strip() 33 | 34 | if len(request.executor_id.strip()) > 0: 35 | executor_id = request.executor_id.strip() 36 | else: 37 | executor_id = hashlib.md5(f"{goal}-{datetime.now()}".encode()).hexdigest() 38 | 39 | task_id = None 40 | if request.task_id > 0: 41 | task_id = request.task_id 42 | 43 | dependent_tasks = request.dependent_tasks 44 | enable_skill_library = request.enable_skill_library 45 | skip_gen = request.skip_gen 46 | 47 | retry_num = 0 48 | task_info = None 49 | while retry_num < 3: 50 | try: 51 | task_info = self.agent.execute( 52 | executor_id, 53 | goal, 54 | task, 55 | dependent_tasks, 56 | task_id, 57 | skip_gen, 58 | enable_skill_library, 59 | ) 60 | except Exception as e: 61 | return jarvis_pb2.ExecuteResponse( 62 | executor_id=executor_id, 63 | task_id=task_id, 64 | task=task, 65 | result="", 66 | error=str(e), 67 | ) 68 | 69 | if task_info is not None and task_info.result != EMPTY_FIELD_INDICATOR: 70 | break 71 | print(f"Retring.... cause of empty result of task: {task_info}") 72 | retry_num += 1 73 | 74 | if retry_num >= 3: 75 | return jarvis_pb2.ExecuteResponse( 76 | executor_id=executor_id, 77 | task_id=task_id, 78 | task=task, 79 | result="", 80 | error="failed to get execution result", 81 | ) 82 | 83 | return jarvis_pb2.ExecuteResponse( 84 | executor_id=executor_id, 85 | task_id=task_info.task_num, 86 | task=task_info.task, 87 | result=task_info.result, 88 | ) 89 | 90 | def ExecutePlan(self, request, context): 91 | if len(request.goal.strip()) <= 0: 92 | return jarvis_pb2.ExecuteResponse( 93 | error="goal is not provided", 94 | ) 95 | goal = request.goal.strip() 96 | 97 | if len(request.executor_id.strip()) > 0: 98 | executor_id = request.executor_id.strip() 99 | else: 100 | executor_id = hashlib.md5(f"{goal}-{datetime.now()}".encode()).hexdigest() 101 | 102 | skip_gen = request.skip_gen 103 | try: 104 | exec_result = self.agent.execute_with_plan( 105 | executor_id, 106 | goal, 107 | skip_gen=skip_gen, 108 | ) 109 | except Exception as e: 110 | logging.error(f"Failed to execute goal: {goal}, error: {e}") 111 | logging.error(traceback.format_exc()) 112 | return jarvis_pb2.ExecuteResponse( 113 | executor_id=executor_id, 114 | goal=goal, 115 | error=str(e), 116 | ) 117 | 118 | response = jarvis_pb2.ExecuteResponse( 119 | executor_id=executor_id, 120 | goal=goal, 121 | result=exec_result.result, 122 | ) 123 | if exec_result.error is not None: 124 | response.error = exec_result.error 125 | 126 | for task_info in exec_result.task_infos: 127 | task_response = jarvis_pb2.ExecuteResponse( 128 | task=task_info.task, 129 | result=task_info.result, 130 | ) 131 | if task_info.error is not None: 132 | task_response.error = task_info.error 133 | 134 | response.subtasks.append(task_response) 135 | return response 136 | 137 | def SaveSkill(self, request, context): 138 | if len(request.executor_id.strip()) <= 0: 139 | return jarvis_pb2.SaveSkillResponse( 140 | error="executor_id is not provided", 141 | ) 142 | executor_id = request.executor_id.strip() 143 | skill_name = request.skill_name.strip() 144 | 145 | try: 146 | skill_name = self.agent.save_skill(executor_id, skill_name) 147 | except Exception as e: 148 | return jarvis_pb2.SaveSkillResponse( 149 | executor_id=executor_id, 150 | error=str(e), 151 | ) 152 | 153 | return jarvis_pb2.SaveSkillResponse( 154 | executor_id=executor_id, 155 | result=f"skill is saved as {skill_name}", 156 | ) 157 | 158 | 159 | def serve(): 160 | workspace_dir = "workspace" 161 | skill_lib_dir = "skill_library" 162 | os.makedirs(workspace_dir, exist_ok=True) 163 | os.chdir(workspace_dir) 164 | # Logging file name and line number 165 | logging.basicConfig( 166 | level=logging.INFO, 167 | format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s", 168 | filename=f"grpc_jarvis.log", 169 | ) 170 | 171 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 172 | jarvis_pb2_grpc.add_JarvisServicer_to_server( 173 | JarvisServicer(skill_library_dir=skill_lib_dir), server 174 | ) 175 | server.add_insecure_port("[::]:51155") 176 | server.start() 177 | server.wait_for_termination() 178 | 179 | 180 | if __name__ == "__main__": 181 | serve() 182 | -------------------------------------------------------------------------------- /jarvis/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name="jarvis-agent", 8 | version="0.1", 9 | long_description=long_description, 10 | long_description_content_type="text/markdown", 11 | packages=find_packages(), 12 | python_requires='>=3.10', 13 | ) -------------------------------------------------------------------------------- /jarvis/smartgpt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaut/jarvis/ac36633d5a966aa9cf20525b393638940675ccbd/jarvis/smartgpt/__init__.py -------------------------------------------------------------------------------- /jarvis/smartgpt/clarifier.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | from jarvis.smartgpt import gpt 4 | from jarvis.smartgpt import preprompts 5 | 6 | 7 | def clarify(goal: str, model: str) -> List[Dict[str, str]]: 8 | messages = [{"role": "system", "content": preprompts.get("clarify_sys")}] 9 | user_input = f"The goal:\n{goal}\n" 10 | 11 | while True: 12 | print() 13 | messages = gpt.chat(model, messages, user_input) 14 | if messages[-1]["content"].strip() == "Nothing more to clarify.": 15 | break 16 | if messages[-1]["content"].strip().lower().startswith("no"): 17 | print("Nothing more to clarify.") 18 | break 19 | 20 | print(messages[-1]["content"]) 21 | user_input = input('\n(answer in text, or "c" to move on)\n') 22 | 23 | if not user_input or user_input == "c": 24 | print() 25 | print("\n(letting Jarvis make its own assumptions)\n") 26 | messages = gpt.chat( 27 | model, 28 | messages, 29 | "Make your own assumptions and state them explicitly before starting", 30 | ) 31 | return messages 32 | 33 | user_input += preprompts.get("clarify_user") 34 | 35 | return messages 36 | 37 | 38 | def clarify_and_summarize(goal: str, model: str) -> str: 39 | # Interactively clarify user goals 40 | messages = clarify(goal, model) 41 | 42 | # Summarize the messages to a clear goal 43 | messages = [ 44 | {"role": "system", "content": "You are an AI assistant to clarify user's goal"} 45 | ] + messages[1:] 46 | 47 | user_prompt = ( 48 | "Summary the goal into a single sentence to make it clear and detailed" 49 | ) 50 | resp = gpt.complete_with_messages(model, messages, user_prompt) 51 | return resp 52 | -------------------------------------------------------------------------------- /jarvis/smartgpt/compiler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from typing import Dict, List, Optional 4 | 5 | import yaml 6 | 7 | from jarvis.smartgpt.translator import Translator 8 | 9 | 10 | class Compiler: 11 | def __init__(self, translator_model: str): 12 | self.translator = Translator(translator_model) 13 | 14 | def load_yaml(self, file_name: str) -> Dict: 15 | try: 16 | with open(file_name, "r") as stream: 17 | return yaml.safe_load(stream) 18 | except Exception as e: 19 | logging.error(f"Error loading file {file_name}: {e}") 20 | raise 21 | 22 | def write_yaml(self, file_name: str, data: str) -> None: 23 | try: 24 | with open(file_name, "w") as stream: 25 | stream.write(data) 26 | except Exception as e: 27 | logging.error(f"Error writing to file {file_name}: {e}") 28 | raise 29 | 30 | def create_task_info( 31 | self, task, objective, num, hints, previous_outcomes, goal 32 | ) -> Dict: 33 | return { 34 | "first_task": num == 1, 35 | "task_num": num, 36 | "hints": hints, 37 | "task": task, 38 | "objective": objective, 39 | "start_seq": (num - 1 << 4) + 1, 40 | "previous_outcomes": previous_outcomes, 41 | "goal": goal, 42 | } 43 | 44 | def check_outcome_changed(self, task_outcome, origin) -> bool: 45 | return task_outcome["overall_outcome"] != origin["overall_outcome"] 46 | 47 | def compile_plan(self) -> List[Dict]: 48 | plan = self.load_yaml("plan.yaml") 49 | hints = plan.get("hints_from_user", []) 50 | goal = plan.get("goal", "") 51 | task_list = plan.get("task_list", []) 52 | task_dependency = plan.get("task_dependency", {}) 53 | 54 | task_outcomes = {} 55 | result = [] 56 | 57 | for task in task_list: 58 | num = task["task_num"] 59 | deps = task_dependency.get(str(num), []) 60 | previous_outcomes = [task_outcomes[i] for i in deps] 61 | 62 | task_info = self.create_task_info( 63 | task["task"], task["objective"], num, hints, previous_outcomes, goal 64 | ) 65 | instructions_yaml_str = self.translator.translate_to_instructions(task_info) 66 | 67 | self.write_yaml(f"{num}.yaml", instructions_yaml_str) 68 | 69 | task_instrs = yaml.safe_load(instructions_yaml_str) 70 | result.append(task_instrs) 71 | 72 | task_outcomes[num] = { 73 | "task_num": num, 74 | "task": task_instrs["task"], 75 | "outcome": task_instrs["overall_outcome"], 76 | } 77 | 78 | return result 79 | 80 | def compile_task_in_plan(self, specified_task_num: int) -> List[Dict]: 81 | plan = self.load_yaml("plan.yaml") 82 | hints = plan.get("hints_from_user", []) 83 | goal = plan.get("goal", "") 84 | task_list = plan.get("task_list", []) 85 | task_dependency = plan.get("task_dependency", {}) 86 | 87 | task_outcomes = {} 88 | result = [] 89 | recompile_subsequent_tasks = False 90 | 91 | for task in task_list: 92 | num = task["task_num"] 93 | deps = task_dependency.get(str(num), []) 94 | previous_outcomes = [task_outcomes[i] for i in deps] 95 | file_name = f"{num}.yaml" 96 | 97 | origin = self.load_yaml(file_name) if os.path.exists(file_name) else None 98 | 99 | task_instrs = None 100 | if num < specified_task_num and os.path.exists(file_name): 101 | task_instrs = self.load_yaml(file_name) 102 | elif ( 103 | num > specified_task_num 104 | and os.path.exists(file_name) 105 | and not recompile_subsequent_tasks 106 | ): 107 | task_instrs = self.load_yaml(file_name) 108 | 109 | if not task_instrs: 110 | task_info = self.create_task_info( 111 | task["task"], task["objective"], num, hints, previous_outcomes, goal 112 | ) 113 | instructions_yaml_str = self.translator.translate_to_instructions( 114 | task_info 115 | ) 116 | self.write_yaml(file_name, instructions_yaml_str) 117 | task_instrs = yaml.safe_load(instructions_yaml_str) 118 | 119 | result.append(task_instrs) 120 | 121 | task_outcomes[num] = { 122 | "task_num": num, 123 | "task": task_instrs["task"], 124 | "outcome": task_instrs["overall_outcome"], 125 | } 126 | 127 | if num == specified_task_num: 128 | recompile_subsequent_tasks = ( 129 | self.check_outcome_changed(task_instrs, origin) if origin else True 130 | ) 131 | 132 | return result 133 | 134 | def compile_task( 135 | self, 136 | task_num: int, 137 | task: str, 138 | goal: str, 139 | previous_outcomes: List, 140 | hints: Optional[List] = [], 141 | objective: Optional[str] = "", 142 | reference: Optional[str] = None, 143 | ) -> Dict: 144 | task_info = self.create_task_info( 145 | task, objective, task_num, hints, previous_outcomes, goal 146 | ) 147 | if reference: 148 | task_info["reference_example"] = reference 149 | instructions_yaml_str = self.translator.translate_to_instructions(task_info) 150 | self.write_yaml(f"{task_num}.yaml", instructions_yaml_str) 151 | result = yaml.safe_load(instructions_yaml_str) 152 | return result 153 | -------------------------------------------------------------------------------- /jarvis/smartgpt/fewshot.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jarvis.smartgpt import utils 4 | 5 | # Initialize the singleton to None 6 | _examples_db = None 7 | 8 | 9 | def init(): 10 | global _examples_db 11 | example_dir_path = os.path.join(os.getcwd(), "data/examples") 12 | 13 | # Check if example_dir_path exists 14 | if not os.path.exists(example_dir_path): 15 | raise FileNotFoundError(f"The directory '{example_dir_path}' does not exist.") 16 | 17 | if _examples_db is None: 18 | _examples_db = utils.DB(example_dir_path) 19 | 20 | 21 | def get(example_name: str): 22 | if _examples_db is None: 23 | raise RuntimeError("Examples database not initialized") 24 | return _examples_db.get(example_name, "") 25 | -------------------------------------------------------------------------------- /jarvis/smartgpt/initializer.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | from jarvis.smartgpt.preprompts import init as init_prompts 4 | from jarvis.smartgpt.fewshot import init as init_examples 5 | 6 | # Load the users .env file into environment variables 7 | load_dotenv(verbose=True, override=True) 8 | del load_dotenv 9 | 10 | 11 | def setup(): 12 | init_prompts() 13 | init_examples() 14 | -------------------------------------------------------------------------------- /jarvis/smartgpt/instruction.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | from jarvis.smartgpt import actions 5 | from jarvis.smartgpt import jvm 6 | from jarvis.smartgpt import utils 7 | 8 | 9 | class JVMInstruction: 10 | def __init__(self, instruction, act, task): 11 | self.instruction = instruction 12 | self.act = act 13 | self.task = task 14 | 15 | def execute(self): 16 | action_type = self.instruction.get("type") 17 | 18 | action_class = self.act.get(action_type) 19 | if action_class is None: 20 | print(f"Unknown action type: {action_type}") 21 | return 22 | 23 | action_id = self.instruction.get("seq") 24 | # clone the args dict! 25 | args = dict(self.instruction.get("args")) 26 | 27 | if action_type == "WebSearch": 28 | args["query"] = self.eval_and_patch(args["query"]) 29 | args["save_to"] = self.eval_and_patch(args["save_to"]) 30 | 31 | if action_type == "FetchWebContent": 32 | args["url"] = self.eval_and_patch(args["url"]) 33 | args["save_to"] = self.eval_and_patch(args["save_to"]) 34 | 35 | if action_type == "RunPython": 36 | # if file_name is empty, use the default file 37 | file_name = args.get("file_name") 38 | if file_name is None or file_name == "": 39 | args["file_name"] = f"tmp_{action_id}.py" 40 | # if timeout is empty, use the default timeout 41 | timeout = args.get("timeout") 42 | if timeout is None or timeout == "": 43 | args["timeout"] = 30 44 | 45 | if action_type == "TextCompletion": 46 | args["request"] = self.eval_and_patch(args.get("request")) 47 | args["content"] = self.eval_and_patch(args.get("content")) 48 | args["output_format"] = self.eval_and_patch( 49 | json.dumps(args.get("output_format"), indent=2) 50 | ) 51 | 52 | action_data = {"type": action_type, "action_id": action_id} 53 | action_data.update(args) 54 | 55 | action = actions.Action.from_dict(action_data) 56 | if action is None: 57 | print(f"Failed to create action from data: {action_data}") 58 | return 59 | 60 | logging.info(f"Running action: {action}\n") 61 | result = action.run() 62 | logging.info(f"\nresult of {action_type}: {result}\n") 63 | 64 | if action_type != "RunPython": 65 | self.post_exec(result) 66 | else: 67 | jvm.load_kv_store() 68 | 69 | def eval_and_patch(self, text) -> str: 70 | if text is None: 71 | return "" 72 | 73 | while True: 74 | tmp_text = jvm.eval(text) 75 | if tmp_text is None: 76 | break 77 | text = tmp_text 78 | 79 | return text 80 | 81 | def post_exec(self, result: str): 82 | try: 83 | data = json.loads(result) 84 | except json.JSONDecodeError as e: 85 | logging.error(f"Failed to parse: {result}, error: {e}") 86 | raise 87 | 88 | # Check if "kvs" key exists in the result 89 | if "kvs" not in data: 90 | logging.error(f"No 'kvs' in the result: {result}") 91 | return 92 | 93 | # Iterate over key-value pairs and set them in the jvm 94 | for kv in data["kvs"]: 95 | try: 96 | key = kv["key"] 97 | value = kv["value"] 98 | except KeyError: 99 | logging.error(f"Invalid KV item in the result: {kv}") 100 | return 101 | 102 | logging.info(f"Setting KV in the JVM database: '{key}'={value}") 103 | jvm.set(key, value) 104 | 105 | 106 | class JVMInterpreter: 107 | def __init__(self): 108 | self.pc = 0 109 | self.actions = { 110 | "WebSearch": actions.WebSearchAction, 111 | "FetchWebContent": actions.FetchWebContentAction, 112 | "RunPython": actions.RunPythonAction, 113 | "TextCompletion": actions.TextCompletionAction, 114 | } 115 | 116 | jvm.load_kv_store() 117 | actions.disable_cache() 118 | actions.load_cache() 119 | jvm.set_loop_idx(0) 120 | 121 | def run(self, instrs, task): 122 | if instrs is not None: 123 | while self.pc < len(instrs): 124 | logging.info( 125 | f"Running Instruction [pc={self.pc}, seq={instrs[self.pc].get('seq')}]: \n{instrs[self.pc]}" 126 | ) 127 | jvm_instruction = JVMInstruction(instrs[self.pc], self.actions, task) 128 | 129 | action_type = instrs[self.pc].get("type") 130 | if action_type == "If": 131 | self.conditional(jvm_instruction) 132 | elif action_type == "Loop": 133 | self.loop(jvm_instruction) 134 | else: 135 | jvm_instruction.execute() 136 | self.pc += 1 137 | 138 | def loop(self, jvm_instruction: JVMInstruction): 139 | args = jvm_instruction.instruction.get("args", {}) 140 | # Extract the count and the list of instructions for the loop 141 | loop_count = 0 142 | # if loop_count is integer 143 | logging.info( 144 | f"loop instruction (seq={jvm_instruction.instruction.get('seq', 'N/A')}) args: {args}" 145 | ) 146 | if isinstance(args["count"], int): 147 | loop_count = args["count"] 148 | elif isinstance(args["count"], str): 149 | if args["count"].isdigit(): 150 | loop_count = int(args["count"]) 151 | else: 152 | # loop_count needs to be evaluated in the context of jvm 153 | loop_count = jvm.eval(args["count"]) 154 | if loop_count is None: 155 | loop_count = 0 156 | else: 157 | loop_count = int(loop_count) 158 | 159 | loop_instructions = args.get("instructions", []) 160 | logging.debug(f"Looping: {loop_instructions}") 161 | 162 | # Execute the loop instructions the given number of times 163 | old_pc = self.pc 164 | for i in range(loop_count): 165 | # Set the loop index in jvm, to adopt gpt behaviour error 166 | jvm.set_loop_idx(i) 167 | # logging.info(f"loop idx: {i}") 168 | # As each loop execution should start from the first instruction, we reset the program counter 169 | self.pc = 0 170 | self.run(loop_instructions, jvm_instruction.task) 171 | self.pc = old_pc 172 | 173 | def conditional(self, jvm_instruction: JVMInstruction): 174 | condition = jvm_instruction.instruction.get("args", {}).get("condition", None) 175 | condition = jvm_instruction.eval_and_patch(condition) 176 | 177 | evaluation_action = actions.TextCompletionAction( 178 | action_id=-1, 179 | request="Judging true or false based on input content", 180 | content=condition, 181 | output_format=json.dumps( 182 | {"kvs": [{"key": "result.seq0.bool", "value": ""}]}, indent=2 183 | ), 184 | ) 185 | 186 | try: 187 | evaluation_result = evaluation_action.run() 188 | output_res = json.loads(evaluation_result) 189 | condition_eval_result = utils.str_to_bool(output_res["kvs"][0]["value"]) 190 | 191 | except Exception as err: 192 | logging.error( 193 | f"Failed to decode AI model response: {condition} with error: {err}" 194 | ) 195 | raise 196 | 197 | logging.info(f"The condition is evaluated to {condition_eval_result}.") 198 | 199 | old_pc = self.pc 200 | if condition_eval_result: 201 | # instruction.instruction["then"] is a list of instructions 202 | if "then" in jvm_instruction.instruction.get("args", {}): 203 | self.pc = 0 204 | self.run( 205 | jvm_instruction.instruction["args"]["then"], jvm_instruction.task 206 | ) 207 | else: 208 | # maybe use pc to jump is a better idea. 209 | if "else" in jvm_instruction.instruction.get("args", {}): 210 | self.pc = 0 211 | self.run( 212 | jvm_instruction.instruction["args"]["else"], jvm_instruction.task 213 | ) 214 | self.pc = old_pc 215 | 216 | def reset(self): 217 | self.pc = 0 218 | jvm.set_loop_idx(0) 219 | -------------------------------------------------------------------------------- /jarvis/smartgpt/jvm.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ast 3 | import logging 4 | import json 5 | 6 | from jarvis.smartgpt import utils 7 | 8 | # initialize a dictionary when the module is imported 9 | kv_store_file = "kv_store.json" 10 | kv_store = {} 11 | 12 | 13 | def reset_kv_store(): 14 | global kv_store 15 | kv_store = {} 16 | save_kv_store() 17 | 18 | 19 | def load_kv_store(): 20 | global kv_store 21 | # Load the kv_store dictionary from the file if it exists 22 | if os.path.exists(kv_store_file): 23 | with open(kv_store_file, "r") as f: 24 | kv_store = json.load(f) 25 | else: 26 | kv_store = {} 27 | 28 | 29 | def save_kv_store(): 30 | with open(kv_store_file, "w") as f: 31 | json.dump(kv_store, f) 32 | 33 | 34 | def get(key, default=None): 35 | try: 36 | value = kv_store.get(key, None) 37 | if value is None: 38 | return default 39 | if isinstance(value, str) and value.startswith("[") and value.endswith("]"): 40 | # This is a list 41 | return list(ast.literal_eval(value)) 42 | return value 43 | except Exception as err: 44 | logging.fatal(f"get, An error occurred: {err}") 45 | return default 46 | 47 | 48 | def set(key, value): 49 | try: 50 | if isinstance(value, list): 51 | value = repr(value) 52 | kv_store[key] = value 53 | save_kv_store() 54 | except Exception as err: 55 | logging.fatal(f"set, An error occurred: {err}") 56 | 57 | 58 | def list_values_with_key_prefix(prefix): 59 | try: 60 | values = [] 61 | for key in kv_store.keys(): 62 | if key.startswith(prefix): 63 | values.append(get(key)) 64 | # logging.info(f"list_values_with_key_prefix, values: {values}") 65 | return values 66 | except Exception as err: 67 | logging.fatal(f"list_values_with_key_prefix, An error occurred: {err}") 68 | return [] 69 | 70 | 71 | def list_keys_with_prefix(prefix): 72 | try: 73 | keys = [key for key in kv_store.keys() if key.startswith(prefix)] 74 | return keys 75 | except Exception as err: 76 | logging.fatal(f"list_keys_with_prefix, An error occurred: {err}") 77 | return [] 78 | 79 | 80 | def set_loop_idx(value): 81 | set("idx", value) 82 | 83 | 84 | LAZY_EVAL_PREFIX = "jvm.eval(" 85 | 86 | 87 | def eval(text, lazy_eval_prefix=LAZY_EVAL_PREFIX): 88 | if not isinstance(text, str): 89 | return text 90 | 91 | if not text.startswith("jvm.get("): 92 | # find last occurrence of "jvm.eval(" 93 | start = text.rfind(lazy_eval_prefix) 94 | if start == -1: 95 | return None 96 | prefix_len = len(lazy_eval_prefix) 97 | # find the corresponding closing tag with parentheses balance 98 | rest = text[start + prefix_len :] 99 | balance = 0 100 | end = 0 101 | for char in rest: 102 | if char == "(": 103 | balance += 1 104 | elif char == ")": 105 | if balance == 0: 106 | break 107 | balance -= 1 108 | end += 1 109 | 110 | if balance != 0: 111 | logging.critical(f"Error: parentheses are not balanced in {text}") 112 | return None 113 | 114 | logging.debug( 115 | f"eval_and_patch_template_before_exec, {start}-{end} text: {text}\n" 116 | ) 117 | 118 | # adjust the end position relative to the original string 119 | end = end + start + prefix_len 120 | # evaluate the substring between jvm.eval( and ) 121 | expression = text[start + prefix_len : end].strip() 122 | else: 123 | start = 0 124 | end = len(text) 125 | prefix_len = 0 126 | 127 | expression = text[start + prefix_len : end].strip() 128 | logging.debug(f"eval_and_patch_template_before_exec, expression: {expression}\n") 129 | try: 130 | evaluated = utils.sys_eval(expression) 131 | except Exception as err: 132 | logging.critical(f"Failed to evaluate {expression}. Error: {str(err)}") 133 | return None 134 | 135 | # replace the evaluated part in the original string 136 | text = text[:start] + str(evaluated) + text[end + 1 :] 137 | logging.debug(f"text after patched: {text}\n") 138 | 139 | return text 140 | -------------------------------------------------------------------------------- /jarvis/smartgpt/planner.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | import logging 4 | from collections import defaultdict, deque 5 | from typing import Dict 6 | 7 | import yaml 8 | 9 | from jarvis.smartgpt import gpt 10 | from jarvis.smartgpt import clarifier 11 | from jarvis.smartgpt import preprompts 12 | 13 | 14 | def gen_plan(model: str, goal: str) -> Dict: 15 | if not goal: 16 | # input the goal 17 | input_goal = input("Please input your goal:\n") 18 | goal = clarifier.clarify_and_summarize(input_goal, model) 19 | 20 | try: 21 | logging.info("========================") 22 | logging.info(f"The goal: {goal}") 23 | 24 | system_prompt = preprompts.get("planner_sys") 25 | user_prompt = ( 26 | f'The goal: """\n{goal}\n"""\n\n' 27 | "Please generate the task list that can finish the goal.\n" 28 | "Your YAML response:```yaml\n" 29 | ) 30 | 31 | resp = gpt.complete(user_prompt, model, system_prompt) 32 | 33 | # resp = reorder_tasks(utils.strip_yaml(resp)) 34 | with open("plan.yaml", "w") as stream: 35 | stream.write(resp) 36 | 37 | return yaml.safe_load(resp) 38 | 39 | except Exception as err: 40 | logging.error("Error in main: %s", err) 41 | time.sleep(1) 42 | raise err 43 | 44 | 45 | def reorder_tasks(plan_yaml_str: str) -> str: 46 | try: 47 | plan = yaml.safe_load(plan_yaml_str) 48 | except yaml.YAMLError as err: 49 | logging.error(f"Error loading plan file: {err}") 50 | raise 51 | 52 | task_list = plan.get("task_list", []) 53 | graph = plan.get("task_dependency", {}) 54 | 55 | # Initialize a dictionary to hold the in-degree of all nodes 56 | in_degree = {task["task_num"]: 0 for task in task_list} 57 | # Initialize a dictionary to hold the out edges of all nodes 58 | out_edges = defaultdict(list) 59 | 60 | # Calculate in-degrees and out edges for all nodes 61 | for task_id, dependencies in graph.items(): 62 | task_id = int(task_id) # convert to integer as in task_list it's integer 63 | for dependency in dependencies: 64 | out_edges[dependency].append(task_id) 65 | in_degree[task_id] += 1 66 | 67 | # Use a queue to hold all nodes with in-degree 0 68 | queue = deque([task_id for task_id in in_degree if in_degree[task_id] == 0]) 69 | sorted_task_list = [] 70 | 71 | # Perform the topological sort 72 | while queue: 73 | task_id = queue.popleft() 74 | sorted_task_list.append(task_id) 75 | 76 | for next_task in out_edges[task_id]: 77 | in_degree[next_task] -= 1 78 | if in_degree[next_task] == 0: 79 | queue.append(next_task) 80 | 81 | # Check if graph contains a cycle 82 | if len(sorted_task_list) != len(in_degree): 83 | logging.error("The plan cannot be sorted due to cyclic dependencies.") 84 | return plan_yaml_str 85 | 86 | # Generate a map from old task IDs to new task IDs 87 | id_map = {old_id: new_id for new_id, old_id in enumerate(sorted_task_list, start=1)} 88 | 89 | # Update task IDs in the task list 90 | for task in task_list: 91 | task["task_num"] = id_map[task["task_num"]] 92 | 93 | # Sort task list by task_num 94 | plan["task_list"] = sorted(task_list, key=lambda task: task["task_num"]) 95 | 96 | # Update task IDs in the task dependency list 97 | new_task_dependency = { 98 | str(id_map[int(task_id)]): [id_map[dep] for dep in deps] 99 | for task_id, deps in graph.items() 100 | } 101 | plan["task_dependency"] = new_task_dependency 102 | 103 | # Dump the updated plan back to YAML 104 | sorted_plan_yaml_str = yaml.dump(plan, sort_keys=False) 105 | return sorted_plan_yaml_str 106 | 107 | 108 | def evaluate_plan(model: str, goal: str): 109 | try: 110 | with open("plan.yaml", "r") as file: 111 | plan = file.read() 112 | except Exception as e: 113 | logging.error(f"Error loading 'plan.yaml' in current workdir, Error: {e}") 114 | return None 115 | 116 | messages = [] 117 | messages.append({"role": "system", "content": preprompts.get("plan_eval_sys")}) 118 | user_prompt = preprompts.get("plan_eval_user").format(goal=goal, plan=plan) 119 | messages.append({"role": "user", "content": user_prompt}) 120 | resp = gpt.send_messages(messages, model) 121 | messages.append({"role": "assistant", "content": resp}) 122 | 123 | match_answer = re.match(r"(yes|no)", resp.lower()) 124 | return match_answer.group(1) if match_answer else "no" 125 | -------------------------------------------------------------------------------- /jarvis/smartgpt/preprompts.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from jarvis.smartgpt import utils 4 | 5 | # Initialize the singleton to None 6 | _prompts_db = None 7 | 8 | 9 | def init(): 10 | global _prompts_db 11 | prompt_dir_path = os.path.join(os.getcwd(), "data/prompts") 12 | 13 | # Check if prompt_dir_path exists 14 | if not os.path.exists(prompt_dir_path): 15 | raise FileNotFoundError(f"The directory '{prompt_dir_path}' does not exist.") 16 | 17 | if _prompts_db is None: 18 | _prompts_db = utils.DB(prompt_dir_path) 19 | 20 | 21 | def get(prompt_name: str): 22 | if _prompts_db is None: 23 | raise RuntimeError("Prompts database not initialized") 24 | return _prompts_db.get(prompt_name, "") 25 | -------------------------------------------------------------------------------- /jarvis/smartgpt/reviewer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | from typing import List, Dict, Tuple 4 | from abc import ABC, abstractmethod 5 | 6 | import yaml 7 | 8 | from jarvis.smartgpt import gpt 9 | from jarvis.smartgpt import utils 10 | from jarvis.smartgpt import preprompts 11 | 12 | 13 | REVIEW_REPEATED_COUNT = 1 14 | 15 | 16 | class Reviewer(ABC): 17 | def __init__(self, model): 18 | self.model = model 19 | 20 | @abstractmethod 21 | def review(self, instrs: str) -> Tuple[bool, str, List[Dict]]: 22 | pass 23 | 24 | def buildSystemMessages(self) -> List[Dict]: 25 | messages = [] 26 | prompt = preprompts.get("reviewer_sys") + "\n" + preprompts.get("jvm_spec") 27 | messages.append({"role": "system", "content": prompt}) 28 | return messages 29 | 30 | def generalReview( 31 | self, instrs: str, review_prompt: str 32 | ) -> Tuple[bool, str, List[Dict]]: 33 | messages = self.buildSystemMessages() 34 | 35 | review_content = preprompts.get(review_prompt).format( 36 | instructions="\n".join(" " + line for line in instrs.splitlines()) 37 | ) 38 | messages.append({"role": "user", "content": review_content}) 39 | 40 | review_response = gpt.send_messages(messages, self.model) 41 | messages.append({"role": "assistant", "content": review_response}) 42 | 43 | review_response = utils.strip_yaml(review_response) 44 | result = yaml.safe_load(review_response) 45 | 46 | if result.get("approved", True): 47 | return True, "", messages 48 | else: 49 | if "review_comment" in result: 50 | return False, result.get("review_comment"), messages 51 | else: 52 | return True, "", messages 53 | 54 | 55 | class EvalSyntaxReviewer(Reviewer): 56 | def review(self, instrs: str) -> Tuple[bool, str, List[Dict]]: 57 | return self.generalReview(instrs, "reviewer_eval_syntax") 58 | 59 | 60 | class LoopIndexKeyReviewer(Reviewer): 61 | def review(self, instrs: str) -> Tuple[bool, str, List[Dict]]: 62 | return self.generalReview(instrs, "reviewer_index_key") 63 | 64 | 65 | class SimulationReviewer(Reviewer): 66 | def review(self, instrs: str) -> Tuple[bool, str, List[Dict]]: 67 | return self._review(instrs, REVIEW_REPEATED_COUNT) 68 | 69 | def _review(self, instrs: str, count: int) -> Tuple[bool, str, List[Dict]]: 70 | messages = [] 71 | messages.append( 72 | {"role": "system", "content": preprompts.get("reviewer_simulation_sys")} 73 | ) 74 | 75 | review_content = preprompts.get("reviewer_simulation_user").format( 76 | instructions=instrs 77 | ) 78 | messages.append({"role": "user", "content": review_content}) 79 | 80 | response = gpt.send_messages(messages, self.model) 81 | messages.append({"role": "assistant", "content": response}) 82 | 83 | messages.append( 84 | {"role": "user", "content": preprompts.get("reviewer_simulation_output")} 85 | ) 86 | response = gpt.send_messages(messages, self.model) 87 | messages.append({"role": "assistant", "content": response}) 88 | 89 | if "CORRECT!" in response: 90 | logging.info( 91 | f"The #{REVIEW_REPEATED_COUNT - count + 1}/{REVIEW_REPEATED_COUNT} round review of Simulation Reviewer says LGTM." 92 | ) 93 | if count - 1 == 0: 94 | return True, "", messages 95 | return self._review(instrs, count - 1) 96 | 97 | match = re.search(r"\"{3}(.*?)\"{3}", response, re.DOTALL) 98 | if match: 99 | extracted_text = match.group(1).strip() 100 | else: 101 | logging.info("No feedback text found between triple quotes.") 102 | return True, "", messages 103 | 104 | review_feedback = ( 105 | f'The Simulation Reviewer\'s feedback:\n"""\n{extracted_text}\n"""' 106 | ) 107 | logging.info(review_feedback) 108 | return False, review_feedback, messages 109 | 110 | 111 | class SyntaxReviewer(Reviewer): 112 | def review(self, instrs: str) -> Tuple[bool, str, List[Dict]]: 113 | messages = [] 114 | messages.append( 115 | {"role": "system", "content": preprompts.get("reviewer_syntax_sys")} 116 | ) 117 | messages.append( 118 | { 119 | "role": "user", 120 | "content": preprompts.get("reviewer_syntax_user").format( 121 | instructions=instrs 122 | ), 123 | } 124 | ) 125 | 126 | resp = gpt.send_messages(messages, self.model) 127 | messages.append({"role": "assistant", "content": resp}) 128 | 129 | if "CORRECT!" in resp: 130 | logging.info("Syntax Reviewer says LGTM.") 131 | return True, "", messages 132 | 133 | match = re.search(r"\"{3}(.*?)\"{3}", resp, re.DOTALL) 134 | if match: 135 | extracted_text = match.group(1).strip() 136 | else: 137 | logging.info("No feedback text found between triple quotes.") 138 | return True, "", messages 139 | 140 | review_feedback = ( 141 | f'The Syntax Reviewer\'s feedback:\n"""\n{extracted_text}\n"""' 142 | ) 143 | logging.info(review_feedback) 144 | return False, review_feedback, messages 145 | -------------------------------------------------------------------------------- /jarvis/smartgpt/spinner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import threading 3 | import itertools 4 | import time 5 | 6 | 7 | class Spinner: 8 | def __init__(self, message="Loading...", delay=0.1): 9 | self.spinner = itertools.cycle(["-", "/", "|", "\\"]) 10 | self.delay = delay 11 | self.message = message 12 | self.running = False 13 | self.spinner_thread = None 14 | 15 | def spin(self): 16 | while self.running: 17 | sys.stdout.write(next(self.spinner) + " " + self.message + "\r") 18 | sys.stdout.flush() 19 | time.sleep(self.delay) 20 | sys.stdout.write("\b" * (len(self.message) + 2)) 21 | 22 | def __enter__(self): 23 | self.running = True 24 | self.spinner_thread = threading.Thread(target=self.spin) 25 | self.spinner_thread.start() 26 | 27 | def __exit__(self, exc_type, exc_value, exc_traceback): 28 | self.running = False 29 | if self.spinner_thread is not None: 30 | self.spinner_thread.join() 31 | sys.stdout.write("\r" + " " * (len(self.message) + 2) + "\r") 32 | sys.stdout.flush() 33 | -------------------------------------------------------------------------------- /jarvis/smartgpt/translator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from typing import List, Dict, Any, Optional 4 | 5 | from jarvis.smartgpt import gpt 6 | from jarvis.smartgpt import utils 7 | from jarvis.smartgpt import fewshot 8 | from jarvis.smartgpt import preprompts 9 | from jarvis.smartgpt import reviewer 10 | from jarvis.utils.tracer import conditional_chan_traceable 11 | 12 | REVIEWER_CLASSES = [ 13 | (reviewer.EvalSyntaxReviewer, [gpt.GPT_3_5_TURBO_16K]), 14 | (reviewer.LoopIndexKeyReviewer, [gpt.GPT_3_5_TURBO_16K]), 15 | ] 16 | 17 | REVIEWER_GPT4_CLASSES = [ 18 | (reviewer.SyntaxReviewer, [gpt.GPT_4]), 19 | # (reviewer.SimulationReviewer, [gpt.GPT_4]), 20 | ] 21 | 22 | DAFAULT_FEW_SHOT_EXAMPLE = "4" 23 | 24 | 25 | class Translator: 26 | def __init__(self, model): 27 | self.model = model 28 | self.reviewers = [cls(*params) for cls, params in REVIEWER_GPT4_CLASSES] 29 | 30 | def build_system_prompt(self, few_shot_data: Optional[str] = None) -> List[Dict]: 31 | messages = [] 32 | fewshot_data = few_shot_data or fewshot.get(DAFAULT_FEW_SHOT_EXAMPLE) 33 | messages.append( 34 | { 35 | "role": "system", 36 | "content": preprompts.get("translator_sys") + "\n" + fewshot_data, 37 | } 38 | ) 39 | return messages 40 | 41 | def prepare_user_hints(self, task_info: Dict[str, Any]): 42 | hints = "" 43 | if task_info.get("first_task", False): 44 | hints = '\n - "This is the first task, so there are no previous tasks or outcomes."' 45 | else: 46 | for item in task_info.get("previous_outcomes", []): 47 | hints += f"\n - \"This is the #{task_info.get('task_num')} task, the previous task #{item.get('task_num')} has outcome: {item.get('outcome')}\"" 48 | 49 | if task_info.get("goal", ""): 50 | hints += f"\n - \"The user's original request: {task_info.get('goal')}\"" 51 | 52 | for item in task_info.get("hints", []): 53 | hints += f'\n - "{item}"' 54 | 55 | if not hints: 56 | hints = "[]" 57 | return hints 58 | 59 | def revise_instructions( 60 | self, task_info: Dict[str, Any], instrs, review_results, review_comments 61 | ): 62 | review_feedbacks = [] 63 | for i, (result, comment) in enumerate(zip(review_results, review_comments)): 64 | if result is False: 65 | review_feedbacks.append(comment) 66 | 67 | if len(review_feedbacks) == 0: 68 | logging.info("No revision required.") 69 | return instrs 70 | 71 | messages = [] 72 | messages.append({"role": "system", "content": preprompts.get("reviser_sys")}) 73 | messages.append( 74 | { 75 | "role": "user", 76 | "content": preprompts.get("reviser_user").format( 77 | instructions=instrs, 78 | review_feedback="\n\n".join(review_feedbacks), 79 | ), 80 | } 81 | ) 82 | 83 | resp = gpt.send_messages(messages, self.model) 84 | messages.append({"role": "asssistant", "content": resp}) 85 | self._trace_reviser_gen(task_info, messages) 86 | 87 | return utils.strip_yaml(resp) 88 | 89 | @conditional_chan_traceable(run_type="chain") 90 | def translate_to_instructions(self, task_info: Dict[str, Any]): 91 | hints = self.prepare_user_hints(task_info) 92 | user_prompt = preprompts.get("translator_user").format( 93 | task_num=task_info.get("task_num", 0), 94 | # task = f"\"{task_info.get('task', '')}\"", 95 | task=json.dumps(task_info.get("task", ""), ensure_ascii=False), 96 | objective=f"\"{task_info.get('objective', '')}\"", 97 | start_seq=task_info.get("start_seq", ""), 98 | hints=hints, 99 | ) 100 | reference_example = task_info.get("reference_example", None) 101 | 102 | logging.info(f"User Prompt: \n{user_prompt}") 103 | 104 | messages = self.build_system_prompt(few_shot_data=reference_example) 105 | messages.append({"role": "user", "content": user_prompt}) 106 | 107 | resp = gpt.send_messages(messages, self.model) 108 | messages.append({"role": "asssistant", "content": resp}) 109 | self._trace_llm_gen(task_info, messages) 110 | 111 | resp = utils.strip_yaml(resp) 112 | logging.info(f"LLM Response: \n{resp}") 113 | 114 | review_results = [] 115 | review_comments = [] 116 | for rv in self.reviewers: 117 | result, comment, messages = rv.review(resp) 118 | self._trace_reviewer_gen(task_info, rv, messages) 119 | review_results.append(result) 120 | review_comments.append(comment) 121 | 122 | return self.revise_instructions( 123 | task_info, resp, review_results, review_comments 124 | ) 125 | 126 | def _trace_llm_gen(self, task_info, messages): 127 | with open(f"review_{task_info.get('task_num', 0)}.txt", "w") as f: 128 | for msg in messages: 129 | f.write(f"{msg['role'].upper()}:\n") 130 | f.write(f"{msg['content']}\n\n") 131 | 132 | def _trace_reviewer_gen(self, task_info, rv, messages): 133 | with open(f"review_{task_info.get('task_num', 0)}.txt", "a") as f: 134 | f.write("=============================================\n") 135 | f.write(f"Reviewer: {rv.__class__.__name__}\n") 136 | f.write("=============================================\n") 137 | for msg in messages: 138 | f.write(f"{msg['role'].upper()}:\n") 139 | f.write(f"{msg['content']}\n\n") 140 | 141 | def _trace_reviser_gen(self, task_info, messages): 142 | with open(f"review_{task_info.get('task_num', 0)}.txt", "a") as f: 143 | f.write("=============================================\n") 144 | f.write("Reviser\n") 145 | f.write("=============================================\n") 146 | for msg in messages: 147 | f.write(f"{msg['role'].upper()}:\n") 148 | f.write(f"{msg['content']}\n\n") 149 | -------------------------------------------------------------------------------- /jarvis/smartgpt/utils.py: -------------------------------------------------------------------------------- 1 | # Description: Utility functions 2 | import re 3 | from pathlib import Path 4 | 5 | from jarvis.smartgpt import jvm 6 | 7 | 8 | def remove_quoted_token(text, token): 9 | pattern = r"([\"\'])" + re.escape(token) + r"\1" 10 | return re.sub(pattern, token, text) 11 | 12 | 13 | def strip_yaml(text): 14 | # Strip whitespace (including newline) from end 15 | text = text.rstrip() 16 | 17 | # keep removing the last "```" if it exists 18 | while text.endswith("```"): 19 | text = text[:-3] 20 | text = text.rstrip() 21 | 22 | # if text starts with "```yaml\n", remove it 23 | if text.startswith("```yaml\n"): 24 | text = text[8:] 25 | 26 | return text 27 | 28 | 29 | def strip_json(text): 30 | # Strip whitespace (including newline) from end 31 | text = text.rstrip() 32 | 33 | # keep removing the last "```" if it exists 34 | while text.endswith("```"): 35 | text = text[:-3] 36 | text = text.rstrip() 37 | 38 | # if text starts with "```yaml\n", remove it 39 | if text.startswith("```json\n"): 40 | text = text[8:] 41 | 42 | return text 43 | 44 | 45 | def sys_eval(text): 46 | return eval(text) 47 | 48 | 49 | def str_to_bool(s): 50 | if isinstance(s, bool): 51 | return s 52 | elif isinstance(s, str): 53 | return s.lower() == "true" 54 | else: 55 | return False 56 | 57 | 58 | class DB: 59 | """A simple key-value store, where keys are filenames and values are file contents.""" 60 | 61 | def __init__(self, path): 62 | self.path = Path(path).absolute() 63 | self.path.mkdir(parents=True, exist_ok=True) 64 | 65 | def __contains__(self, key): 66 | return (self.path / key).is_file() 67 | 68 | def __getitem__(self, key): 69 | full_path = self.path / key 70 | 71 | if not full_path.is_file(): 72 | raise KeyError(f"File '{key}' could not be found in '{self.path}'") 73 | with full_path.open("r", encoding="utf-8") as f: 74 | return f.read() 75 | 76 | def get(self, key, default=None): 77 | try: 78 | return self[key] 79 | except KeyError: 80 | return default 81 | 82 | def __setitem__(self, key, val): 83 | full_path = self.path / key 84 | full_path.parent.mkdir(parents=True, exist_ok=True) 85 | 86 | if isinstance(val, str): 87 | full_path.write_text(val, encoding="utf-8") 88 | else: 89 | # If val is neither a string nor bytes, raise an error. 90 | raise TypeError("val must be either a str or bytes") 91 | -------------------------------------------------------------------------------- /jarvis/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaut/jarvis/ac36633d5a966aa9cf20525b393638940675ccbd/jarvis/utils/__init__.py -------------------------------------------------------------------------------- /jarvis/utils/tracer.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from langsmith.run_helpers import traceable 4 | 5 | 6 | def conditional_chan_traceable(run_type: str): 7 | def decorator(func): 8 | if os.environ.get("LANGCHAIN_TRACING_V2", "false").lower() == "true": 9 | return traceable(run_type=run_type)(func) 10 | return func 11 | 12 | return decorator 13 | -------------------------------------------------------------------------------- /run_goal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -eq 1 ]; then 4 | work_dir=$1 5 | else 6 | work_dir=./workspace 7 | fi 8 | 9 | for file in ${work_dir}/*.yaml 10 | do 11 | idx=$(basename "$file" .yaml) 12 | if [[ $idx =~ ^[0-9]+$ ]] 13 | then 14 | python -m jarvis --workspace=${work_dir} --yaml="${idx}.yaml" 15 | fi 16 | done 17 | -------------------------------------------------------------------------------- /run_skill_chain.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import logging 4 | import glob 5 | import argparse 6 | from jarvis.agent.jarvis_agent import JarvisAgent 7 | import traceback 8 | 9 | 10 | def clear_files_in_directory(directory_path): 11 | file_patterns = ["*.yaml", "*.json", "*.txt"] 12 | for pattern in file_patterns: 13 | files_to_delete = glob.glob(os.path.join(directory_path, pattern)) 14 | for file_path in files_to_delete: 15 | try: 16 | os.remove(file_path) 17 | logging.info(f"Remove file: {file_path}") 18 | except Exception as e: 19 | logging.info(f"Failed to remove {file_path}: {e}") 20 | 21 | 22 | def pretty_output(exec_result): 23 | print("\n" + "-" * 50) 24 | print("Skill Execution Summary") 25 | print("-" * 50 + "\n") 26 | 27 | print(f"Skill Result: {exec_result.result}") 28 | print(f"Skill Error: {exec_result.error}") 29 | 30 | if exec_result.task_infos: 31 | print("\n" + "=" * 50) 32 | print("Detailed Task Infos") 33 | print("=" * 50 + "\n") 34 | 35 | for task_info in exec_result.task_infos: 36 | print(f"Subtask: {task_info.task}") 37 | print(f"Result: {task_info.result}") 38 | print(f"Error: {task_info.error}\n") 39 | print("-" * 50 + "\n") 40 | 41 | print("End of Execution Summary") 42 | print("-" * 50 + "\n") 43 | 44 | 45 | def execute(workspace_dir, skill_lib_dir, execution_dir, skills): 46 | os.makedirs(workspace_dir, exist_ok=True) 47 | os.chdir(workspace_dir) 48 | 49 | # Logging file name and line number 50 | logging.basicConfig( 51 | level=logging.INFO, 52 | format="%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s", 53 | filename="chain_jarvis.log", 54 | ) 55 | 56 | agent = JarvisAgent(skill_lib_dir) 57 | 58 | for index, skill in enumerate(skills): 59 | if index > 0: 60 | logging.info(f"waitting for next skill {skill}") 61 | print(f"waitting for next skill {skill}") 62 | time.sleep(5) 63 | clear_files_in_directory(execution_dir) 64 | logging.info(f"executing skill: {skill}") 65 | print(f"executing skill: {skill}") 66 | 67 | try: 68 | exec_result = agent.execute_skill(execution_dir, skill) 69 | except Exception as e: 70 | logging.error(f"Failed to execute skill: {skill}, error: {e}") 71 | logging.error(traceback.format_exc()) 72 | return 73 | 74 | pretty_output(exec_result) 75 | 76 | 77 | def run(): 78 | parser = argparse.ArgumentParser( 79 | description="CLI for Jarvis Agent Skills Chain Execution" 80 | ) 81 | parser.add_argument("--workspace", required=True, help="Workspace directory path") 82 | parser.add_argument( 83 | "--skill_dir", required=True, help="Skill library directory path" 84 | ) 85 | parser.add_argument( 86 | "--execution_dir", required=True, help="Execution directory path" 87 | ) 88 | parser.add_argument("--skills", required=True, help="Comma separated skills list") 89 | 90 | args = parser.parse_args() 91 | 92 | # Split comma-separated skills into a list 93 | skills_list = args.skills.split(",") 94 | execute(args.workspace, args.skill_dir, args.execution_dir, skills_list) 95 | 96 | 97 | if __name__ == "__main__": 98 | run() 99 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaut/jarvis/ac36633d5a966aa9cf20525b393638940675ccbd/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_actions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | import yaml 5 | 6 | from jarvis.smartgpt.actions import FetchWebContentAction 7 | from jarvis.smartgpt.actions import WebSearchAction 8 | from jarvis.smartgpt.actions import RunPythonAction 9 | from jarvis.smartgpt.actions import TextCompletionAction 10 | 11 | class TestFetchWebContentAction(unittest.TestCase): 12 | def setUp(self): 13 | self.action = FetchWebContentAction(1, "https://news.ycombinator.com/", "content_fetched_0.seq3.str") 14 | 15 | def test_ensure_url_scheme(self): 16 | # Test case where url does not have a scheme 17 | self.assertEqual( 18 | FetchWebContentAction.ensure_url_scheme("www.google.com"), 19 | "https://www.google.com" 20 | ) 21 | 22 | # Test case where url has http scheme 23 | self.assertEqual( 24 | FetchWebContentAction.ensure_url_scheme("http://www.google.com"), 25 | "http://www.google.com" 26 | ) 27 | 28 | # Test case where url has https scheme 29 | self.assertEqual( 30 | FetchWebContentAction.ensure_url_scheme("https://www.google.com"), 31 | "https://www.google.com" 32 | ) 33 | 34 | def test_extract_text(self): 35 | html = """ 36 | 37 | 38 | 39 |

Hello, World!

40 | 41 | 42 | """ 43 | text = FetchWebContentAction.extract_text(html) 44 | self.assertNotIn("console.log('Hello, World!');", text) 45 | self.assertIn("Hello, World!", text) 46 | 47 | @patch.object(FetchWebContentAction, 'get_html', return_value='

Hello World!

') 48 | def test_run(self, mock_get_html): 49 | expected_result = yaml.safe_dump({ 50 | "kvs": [ 51 | {"key": self.action.save_to, "value": "Hello World!"} 52 | ] 53 | }) 54 | self.assertEqual(self.action.run(), expected_result) 55 | mock_get_html.assert_called_once() 56 | 57 | 58 | class TestWebSearchAction(unittest.TestCase): 59 | def setUp(self): 60 | self.action = WebSearchAction(1, "hacker news", "search_url.seq3.list") 61 | 62 | def test_id(self): 63 | self.assertEqual(self.action.id(), 1) 64 | 65 | def test_key(self): 66 | self.assertEqual(self.action.key(), "WebSearch") 67 | 68 | def test_short_string(self): 69 | self.assertEqual(self.action.short_string(), "action_id: 1, Search online for `hacker news`.") 70 | 71 | @patch('smartgpt.actions.requests.get') 72 | @patch('smartgpt.actions.save_to_cache') 73 | @patch('smartgpt.actions.get_from_cache') 74 | def test_run(self, mock_cache_get, mock_cache_save, mock_requests_get): 75 | # assume that there is no cache for the query 76 | mock_cache_get.return_value = None 77 | # assume that the http request was successful 78 | mock_requests_get.return_value.status_code = 200 79 | # assume that the request returns a json response 80 | mock_requests_get.return_value.json.return_value = {"items": [{"link": "https://news.ycombinator.com/"}]} 81 | 82 | result = self.action.run() 83 | expected_result = yaml.safe_dump({"kvs": [{"key": "search_url.seq3.list", "value": ["https://news.ycombinator.com/"]}]}) 84 | 85 | self.assertEqual(result, expected_result) 86 | mock_cache_save.assert_called_once() 87 | 88 | class TestRunPythonAction(unittest.TestCase): 89 | def setUp(self): 90 | # Create a simple action 91 | self.action = RunPythonAction( 92 | action_id=1, 93 | code="print('Hello, World!')", 94 | timeout=5 95 | ) 96 | 97 | def test_run(self): 98 | # Call run method of the action 99 | output = self.action.run() 100 | 101 | # Check if the output is as expected 102 | self.assertIn("#stdout of process:\nHello, World!\n", output) 103 | 104 | def test_run_with_timeout(self): 105 | # Create an action that runs an infinite loop 106 | self.action = RunPythonAction( 107 | action_id=1, 108 | code="while True: pass", 109 | timeout=1 110 | ) 111 | # Call run method of the action 112 | output = self.action.run() 113 | 114 | # Check if the output indicates a timeout error 115 | self.assertIn("timed out after", output) 116 | 117 | def test_run_with_python_error(self): 118 | # Create an action that runs incorrect Python code 119 | self.action = RunPythonAction( 120 | action_id=1, 121 | code="print(1/0)", 122 | timeout=5 123 | ) 124 | # Call run method of the action 125 | output = self.action.run() 126 | 127 | # Check if the output includes Python's ZeroDivisionError message 128 | self.assertIn("ZeroDivisionError", output) 129 | 130 | def test_run_with_dependency(self): 131 | # Create an action that needs the requests package 132 | self.action = RunPythonAction( 133 | action_id=1, 134 | code="import requests", 135 | pkg_dependencies=['requests'], 136 | timeout=5 137 | ) 138 | # Call run method of the action 139 | output = self.action.run() 140 | 141 | # Check if the output does not indicate an ImportError 142 | self.assertNotIn("ImportError", output) 143 | 144 | def test_run_with_dependency_and_usage(self): 145 | # Create an action that needs the numpy package 146 | # and uses it in the code to create an array and print it 147 | self.action = RunPythonAction( 148 | action_id=1, 149 | code="import numpy as np\narr = np.array([1, 2, 3, 4, 5])\nprint(arr)", 150 | pkg_dependencies=['numpy'], 151 | timeout=10 152 | ) 153 | # Call run method of the action 154 | output = self.action.run() 155 | 156 | # Check if the output includes the printed numpy array 157 | self.assertIn("[1 2 3 4 5]", output) 158 | 159 | class TestTextCompletion(unittest.TestCase): 160 | def setUp(self): 161 | self.action = TextCompletionAction(1, "Complete this text", "This is a test", '{"kvs": [{"weather_report.seq3.str":""}]}') 162 | 163 | @patch('smartgpt.gpt.send_message') 164 | @patch('smartgpt.actions.save_to_cache') 165 | @patch('smartgpt.actions.get_from_cache') 166 | def test_run_send_message_error(self, mock_get_from_cache, mock_save_to_cache, mock_send_message): 167 | # Test case where the gpt.send_message method returns None, indicating an error 168 | mock_get_from_cache.return_value = None 169 | mock_send_message.return_value = None 170 | result = self.action.run() 171 | self.assertIn("appears to have failed.", result) 172 | mock_get_from_cache.assert_called_once() 173 | mock_save_to_cache.assert_not_called() 174 | mock_send_message.assert_called_once() 175 | 176 | 177 | if __name__ == "__main__": 178 | unittest.main() 179 | -------------------------------------------------------------------------------- /tests/test_instruction.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | import json 4 | 5 | from jarvis.smartgpt.instruction import JVMInstruction 6 | 7 | class TestInstruction(unittest.TestCase): 8 | def setUp(self): 9 | self.instruction = JVMInstruction(None, None, None) 10 | 11 | def test_eval_and_patch_1(self): 12 | text_input = "{'kvs':[{'key':\"key_points_\" + str(jvm.get(\"idx\")) + \".seq3.list\", 'value':''}, {'key':'features_' + str(jvm.get('idx')) + '.seq3.list', 'value':''}]}" 13 | expected_output = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}, {"key":"features_0.seq3.list", "value":""}]}' 14 | 15 | # Here we should use mock.patch to simulate the behavior of jvm.get 16 | with mock.patch('smartgpt.instruction.jvm.get', return_value=0): 17 | actual_output = self.instruction.eval_and_patch(text_input) 18 | 19 | self.assertEqual(actual_output, expected_output) 20 | 21 | def test_eval_and_patch_2(self): 22 | text_input = '{"kvs":[{"key":"key_points_" + str(jvm.get(\'idx\')) + ".seq3.list", "value":""}, {"key":"features_" + str(jvm.get(\'idx\')) + ".seq3.list", "value":""}]}' 23 | expected_output = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}, {"key":"features_0.seq3.list", "value":""}]}' 24 | 25 | # Here we should use mock.patch to simulate the behavior of jvm.get 26 | with mock.patch('smartgpt.instruction.jvm.get', return_value=0): 27 | actual_output = self.instruction.eval_and_patch(text_input) 28 | 29 | self.assertEqual(actual_output, expected_output) 30 | 31 | def test_eval_and_patch_no_kvs(self): 32 | # Test case where 'kvs' is not in the input text 33 | text_input = '{"some_other_key":[{"key":"key1", "value":"value1"}, {"key":"key2", "value":"value2"}]}' 34 | expected_output = text_input # Expected output is the same as input because 'kvs' is not found 35 | 36 | actual_output = self.instruction.eval_and_patch(text_input) 37 | self.assertEqual(actual_output, expected_output) 38 | 39 | def test_eval_and_patch_multiple_key_value_pairs(self): 40 | text_input = "{'kvs':[{'key': 'jvm.eval(jvm.get(\"key1\"))', 'value': 'value1'}, {'key': 'jvm.eval(jvm.get(\"key2\"))', 'value': 'value2'}]}" 41 | expected_output = '{"kvs":[{"key": "new_key2", "value": "value1"}, {"key": "new_key1", "value": "value2"}]}' 42 | 43 | # Assuming jvm.get("key1") returns "new_key1" and jvm.get("key2") returns "new_key2" 44 | # Considering that jvm.eval() is in a right-to-left order, expected_output needs to pay attention to this 45 | # Here we should use mock.patch to simulate the behavior of jvm.get 46 | with mock.patch('smartgpt.instruction.jvm.get', side_effect=['new_key1', 'new_key2']): 47 | actual_output = self.instruction.eval_and_patch(text_input) 48 | 49 | self.assertEqual(actual_output, expected_output) 50 | 51 | def test_eval_and_patch_empty_kvs(self): 52 | # Test case where 'kvs' exists but has no key-value pairs 53 | text_input = '{"kvs":[]}' 54 | expected_output = text_input # Expected output is the same as input because there are no key-value pairs 55 | 56 | actual_output = self.instruction.eval_and_patch(text_input) 57 | 58 | self.assertEqual(actual_output, expected_output) 59 | 60 | def test_eval_and_patch_non_string_key(self): 61 | # Test case where 'key' in 'kvs' is not a string 62 | text_input = '{"kvs":[{"key":123, "value":"value1"}]}' 63 | expected_output = text_input # Expected output is the same as input because 'key' is not a string 64 | 65 | actual_output = self.instruction.eval_and_patch(text_input) 66 | 67 | self.assertEqual(actual_output, expected_output) 68 | 69 | def test_eval_and_patch_no_key_in_kvs(self): 70 | # Test case where 'key' is not in 'kvs' 71 | text_input = '{"kvs":[{"value":"value1"}]}' 72 | expected_output = text_input # Expected output is the same as input because 'key' is not in 'kvs' 73 | 74 | actual_output = self.instruction.eval_and_patch(text_input) 75 | 76 | self.assertEqual(actual_output, expected_output) 77 | 78 | def test_eval_and_patch_no_value_in_kvs(self): 79 | # Test case where 'value' is not in 'kvs' 80 | text_input = '{"kvs":[{"key":"key1"}]}' 81 | expected_output = text_input # Expected output is the same as input because 'value' is not in 'kvs' 82 | 83 | actual_output = self.instruction.eval_and_patch(text_input) 84 | 85 | self.assertEqual(actual_output, expected_output) 86 | 87 | def test_eval_and_patch_mixed_string_delimiters(self): 88 | # Test case where string delimiters are mixed 89 | text_input = "{'kvs':[{'key':\"key_points_\" + str(jvm.get(\"idx\")) + \".seq3.list\", 'value':''}, {'key':'features_' + str(jvm.get('idx')) + '.seq3.list', 'value':''}]}" 90 | expected_output = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}, {"key":"features_0.seq3.list", "value":""}]}' 91 | 92 | # Here we should use mock.patch to simulate the behavior of jvm.get 93 | with mock.patch('smartgpt.instruction.jvm.get', return_value=0): 94 | actual_output = self.instruction.eval_and_patch(text_input) 95 | 96 | self.assertEqual(actual_output, expected_output) 97 | 98 | def test_eval_and_patch_complex_expression_single_quotes(self): 99 | # Test case where the key expression involves complex operations 100 | text_input = "{'kvs':[{'key':'jvm.eval(\"key_points_\" + str(jvm.get(\"idx\")) + \"_\" + str(jvm.get(\"id\")) + \".seq3.list\")', 'value':''}, {'key':\"features_\" + str(jvm.get(\"id\")) + \"_\" + str(jvm.get(\"idx\")) + \".seq3.list\", 'value':''}]}" 101 | expected_output = '{"kvs":[{"key":"key_points_0_1.seq3.list", "value":""}, {"key":"features_1_0.seq3.list", "value":""}]}' 102 | 103 | # Here we should use mock.patch to simulate the behavior of jvm.get 104 | with mock.patch('smartgpt.instruction.jvm.get', side_effect=[0, 1, 1, 0]): 105 | actual_output = self.instruction.eval_and_patch(text_input) 106 | 107 | self.assertEqual(actual_output, expected_output) 108 | 109 | def test_eval_and_patch_complex_expression_double_quotes(self): 110 | # Test case where the key expression involves complex operations 111 | text_input = '{"kvs":[{"key":"jvm.eval(\'key_points_\' + str(jvm.get(\'idx\')) + \'_\' + str(jvm.get(\'id\')) + \'.seq3.list\')", "value":""}, {"key":\'features_\' + str(jvm.get(\'id\')) + \'_\' + str(jvm.get(\'idx\')) + \'.seq3.list\', "value":""}]}' 112 | expected_output = '{"kvs":[{"key":"key_points_0_1.seq3.list", "value":""}, {"key":"features_1_0.seq3.list", "value":""}]}' 113 | 114 | # Here we should use mock.patch to simulate the behavior of jvm.get 115 | with mock.patch('smartgpt.instruction.jvm.get', side_effect=[0, 1, 1, 0]): 116 | actual_output = self.instruction.eval_and_patch(text_input) 117 | 118 | self.assertEqual(actual_output, expected_output) 119 | 120 | def test_eval_and_patch_get_condition_key(self): 121 | text_input = 'jvm.eval(jvm.get("weather_notes.seq5.str") or jvm.get("weather_notes.seq6.str"))' 122 | expected_output = "the weather is sunny and warm (from weather_notes.seq6.str)" 123 | 124 | # Assuming jvm.get("key1") returns None and jvm.get("key2") returns "the weather is sunny and warm" 125 | # Considering that jvm.eval() is in a right-to-left order, expected_output needs to pay attention to this 126 | # Here we should use mock.patch to simulate the behavior of jvm.get 127 | with mock.patch('smartgpt.instruction.jvm.get', side_effect=["the weather is sunny and warm (from weather_notes.seq6.str)", None]): 128 | actual_output = self.instruction.eval_and_patch(text_input) 129 | 130 | self.assertEqual(actual_output, expected_output) 131 | 132 | expected_output = "the weather is cloudy and cold (from weather_notes.seq5.str)" 133 | with mock.patch('smartgpt.instruction.jvm.get', side_effect=[None, "the weather is cloudy and cold (from weather_notes.seq5.str)"]): 134 | actual_output = self.instruction.eval_and_patch(text_input) 135 | 136 | self.assertEqual(actual_output, expected_output) 137 | 138 | def test_post_exec_valid_json(self): 139 | result = '{"kvs":[{"key": "key1", "value": "value1"}, {"key": "key2", "value": "value2"}]}' 140 | expected_calls = [mock.call("key1", "value1"), mock.call("key2", "value2")] 141 | 142 | with mock.patch('smartgpt.instruction.jvm.set') as mock_set: 143 | self.instruction.post_exec(result) 144 | 145 | mock_set.assert_has_calls(expected_calls, any_order=True) 146 | 147 | def test_post_exec_invalid_json(self): 148 | result = 'Invalid JSON string' 149 | 150 | with mock.patch('smartgpt.instruction.jvm.set') as mock_set: 151 | self.instruction.post_exec(result) 152 | 153 | mock_set.assert_not_called() 154 | 155 | def test_post_exec_no_kvs(self): 156 | result = '{"no_kvs": []}' 157 | 158 | with mock.patch('smartgpt.instruction.jvm.set') as mock_set: 159 | self.instruction.post_exec(result) 160 | 161 | mock_set.assert_not_called() 162 | 163 | def test_post_exec_malformed_kvs(self): 164 | result = '{"kvs":[{"key": "key1", "value": "value1"}, {"malformed": true}]}' 165 | expected_calls = [mock.call("key1", "value1")] 166 | 167 | with mock.patch('smartgpt.instruction.jvm.set') as mock_set: 168 | self.instruction.post_exec(result) 169 | 170 | mock_set.assert_has_calls(expected_calls, any_order=True) 171 | 172 | def test_post_exec_fetch_result(self): 173 | result = {"kvs": [{"key": "search_content_seq0.str", "value": "the quick brown fox jumps over the lazy dog"}]} 174 | result_json_str = json.dumps(result) 175 | expected_calls = [mock.call("search_content_seq0.str", "the quick brown fox jumps over the lazy dog")] 176 | 177 | with mock.patch('smartgpt.instruction.jvm.set') as mock_set: 178 | self.instruction.post_exec(result_json_str) 179 | 180 | mock_set.assert_has_calls(expected_calls, any_order=True) 181 | 182 | def test_post_exec_websearch_result(self): 183 | result = {"kvs": [{"key": "search_url_seq0.list", "value": ["https://wwww.google.com", "https://www.apple.com"]}]} 184 | result_json_str = json.dumps(result) 185 | expected_calls = [mock.call("search_url_seq0.list", ["https://wwww.google.com", "https://www.apple.com"])] 186 | 187 | with mock.patch('smartgpt.instruction.jvm.set') as mock_set: 188 | self.instruction.post_exec(result_json_str) 189 | 190 | mock_set.assert_has_calls(expected_calls, any_order=True) 191 | 192 | if __name__ == "__main__": 193 | unittest.main() 194 | -------------------------------------------------------------------------------- /tests/test_planner.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import yaml 4 | 5 | from jarvis.smartgpt import planner 6 | 7 | 8 | class TestPlaner(unittest.TestCase): 9 | def setUp(self): 10 | self.plan_yaml_str = """ 11 | task_list: 12 | - task_num: 1 13 | task: "abc" 14 | objective: "" 15 | tools: ["RunPython"] 16 | outcome: "" 17 | - task_num: 2 18 | task: "def" 19 | objective: "" 20 | tools: ["RunPython"] 21 | outcome: "" 22 | - task_num: 3 23 | task: "ghi" 24 | objective: "" 25 | tools: ["RunPython"] 26 | outcome: "" 27 | - task_num: 4 28 | task: "jkl" 29 | objective: "" 30 | tools: ["RunPython"] 31 | outcome: "" 32 | task_dependency: 33 | "1": [2] 34 | "3": [2] 35 | "4": [3, 1] 36 | """ 37 | 38 | def test_sort_plan(self): 39 | sorted_plan_yaml_str = planner.reorder_tasks(self.plan_yaml_str) 40 | sorted_plan = yaml.safe_load(sorted_plan_yaml_str) 41 | 42 | # Test if the tasks are sorted correctly 43 | expected_task_order = ["def", "abc", "ghi", "jkl"] 44 | sorted_task_order = [task['task'] for task in sorted_plan['task_list']] 45 | self.assertEqual(expected_task_order, sorted_task_order) 46 | 47 | # Test if the dependencies are updated correctly 48 | expected_dependency = {"2": [1], "3": [1], "4": [3, 2]} 49 | self.assertEqual(expected_dependency, sorted_plan['task_dependency']) 50 | 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # test_utils.py 2 | 3 | import unittest 4 | from unittest.mock import patch 5 | 6 | from jarvis.smartgpt import jvm 7 | 8 | class TestUtils(unittest.TestCase): 9 | @patch('smartgpt.jvm.get', return_value=0) 10 | def test_eval_expression_single(self, mock_get): 11 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list")", "value":""}]}' 12 | expected = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}]}' 13 | self.assertEqual(jvm.eval(text), expected) 14 | 15 | @patch('smartgpt.jvm.get', return_value=0) 16 | def test_eval_expression_multiple(self, mock_get): 17 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list")", "value":""}, {"key":"jvm.eval("key_features_" + str(jvm.get("idx")) + ".seq3.list")", "value":""}]}' 18 | expected_1 = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list")", "value":""}, {"key":"key_features_0.seq3.list", "value":""}]}' 19 | expected_2 = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}, {"key":"key_features_0.seq3.list", "value":""}]}' 20 | self.assertEqual(jvm.eval(text), expected_1) 21 | self.assertEqual(jvm.eval(expected_1), expected_2) 22 | 23 | @patch('smartgpt.jvm.get', return_value=0) 24 | def test_eval_expression_nested(self, mock_get): 25 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq"+ str(jvm.get("idx") + 3) + ".list")", "value":""}]}' 26 | expected = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}]}' 27 | self.assertEqual(jvm.eval(text), expected) 28 | 29 | @patch('smartgpt.jvm.get', return_value=0) 30 | def test_incorrect_parentheses(self, mock_get): 31 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list"", "value":""}]}' 32 | expected = None 33 | self.assertEqual(jvm.eval(text), expected) 34 | 35 | @patch('smartgpt.jvm.get', return_value=0) 36 | def test_non_existent_variable(self, mock_get): 37 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(non_existent_variable) + ".seq3.list")", "value":""}]}' 38 | expected = None 39 | self.assertEqual(jvm.eval(text), expected) 40 | 41 | @patch('smartgpt.jvm.get', return_value=0) 42 | def test_syntax_error(self, mock_get): 43 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + strjvm.get("idx")) + ".seq3.list")", "value":""}]}' 44 | expected = None 45 | self.assertEqual(jvm.eval(text), expected) 46 | 47 | @patch('smartgpt.jvm.get', return_value=0) 48 | def test_incomplete_string(self, mock_get): 49 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx") + ".seq3.list", "value":""}]}' 50 | expected = None 51 | self.assertEqual(jvm.eval(text), expected) 52 | 53 | @patch('smartgpt.jvm.get', return_value=0) 54 | def test_invalid_operation(self, mock_get): 55 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx"))/0 + ".seq3.list")", "value":""}]}' 56 | expected = None 57 | self.assertEqual(jvm.eval(text), expected) 58 | 59 | @patch('smartgpt.jvm.get', return_value=0) 60 | def test_missing_closing_tag(self, mock_get): 61 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list", "value":""}]}' 62 | expected = None 63 | self.assertEqual(jvm.eval(text), expected) 64 | 65 | @patch('smartgpt.jvm.get', return_value=0) 66 | def test_multiple_evals(self, mock_get): 67 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list") + jvm.eval("_extra")", "value":""}]}' 68 | expected_1 = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list") + _extra", "value":""}]}' 69 | expected_2 = '{"kvs":[{"key":"key_points_0.seq3.list + _extra", "value":""}]}' 70 | self.assertEqual(jvm.eval(text), expected_1) 71 | self.assertEqual(jvm.eval(expected_1), expected_2) 72 | 73 | @patch('smartgpt.jvm.get', return_value=0) 74 | def test_nested_evals(self, mock_get): 75 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + "jvm.eval(str(jvm.get("idx")))" + ".seq3.list")", "value":""}]}' 76 | expected_1 = '{"kvs":[{"key":"jvm.eval("key_points_" + "0" + ".seq3.list")", "value":""}]}' 77 | expected_2 = '{"kvs":[{"key":"key_points_0.seq3.list", "value":""}]}' 78 | self.assertEqual(jvm.eval(text), expected_1) 79 | self.assertEqual(jvm.eval(expected_1), expected_2) 80 | 81 | @patch('smartgpt.jvm.get', return_value=1) 82 | def test_eval_with_different_mock_value(self, mock_get): 83 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq3.list")", "value":""}]}' 84 | expected = '{"kvs":[{"key":"key_points_1.seq3.list", "value":""}]}' 85 | self.assertEqual(jvm.eval(text), expected) 86 | 87 | @patch('smartgpt.jvm.get', return_value=1) 88 | def test_eval_without_eval(self, mock_get): 89 | text = '{"kvs":[{"key":"key_points_" + str(jvm.get("idx")) + ".seq3.list", "value":""}]}' 90 | expected = None 91 | self.assertEqual(jvm.eval(text), expected) 92 | 93 | @patch('smartgpt.jvm.get', return_value=0) 94 | def test_multiple_nested_evals(self, mock_get): 95 | text = 'jvm.eval("key_points_" + "jvm.eval(str(jvm.get("jvm.eval("idx")")))" + ".seqjvm.eval(str(1+1+1)).list")' 96 | expected = 'key_points_0.seq3.list' 97 | result = jvm.eval(text) 98 | result = jvm.eval(result) 99 | result = jvm.eval(result) 100 | result = jvm.eval(result) 101 | self.assertEqual(result, expected) 102 | 103 | @patch('smartgpt.jvm.get', return_value=0) 104 | def test_eval_expression_complex(self, mock_get): 105 | text = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq"+ str(jvm.eval(jvm.eval("2*" + str(jvm.get("idx"))))) + ".list")", "value":""}]}' 106 | expected_step_1 = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq"+ str(jvm.eval(2*0)) + ".list")", "value":""}]}' 107 | expected_step_2 = '{"kvs":[{"key":"jvm.eval("key_points_" + str(jvm.get("idx")) + ".seq"+ str(0) + ".list")", "value":""}]}' 108 | expected_step_3 = '{"kvs":[{"key":"key_points_0.seq0.list", "value":""}]}' 109 | 110 | self.assertEqual(jvm.eval(text), expected_step_1) 111 | self.assertEqual(jvm.eval(expected_step_1), expected_step_2) 112 | self.assertEqual(jvm.eval(expected_step_2), expected_step_3) 113 | 114 | 115 | if __name__ == "__main__": 116 | unittest.main() 117 | --------------------------------------------------------------------------------