diff --git a/.env b/.env index 2b44bbf..b6b291f 100644 --- a/.env +++ b/.env @@ -1,3 +1,8 @@ MODEL_NAME="gpt-4-0125-preview" -OPENAI_API_KEY= "" -OPENAI_ORGANIZATION= "" +OPENAI_API_KEY="" +OPENAI_ORGANIZATION="" +API_BASE_URL="http://127.0.0.1:8079" +OPENAI_BASE_URL="" +BING_SUBSCRIPTION_KEY="" +BING_SEARCH_URL="https://api.bing.microsoft.com/v7.0/search" +WOLFRAMALPHA_APP_ID="" \ No newline at end of file diff --git a/README.md b/README.md index f93f014..c17d80c 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ [[Website]](https://os-copilot.github.io/) [[Arxiv]](https://arxiv.org/abs/2402.07456) [[PDF]](https://arxiv.org/pdf/2402.07456.pdf) +[[Documentation]]() -[](https://github.com/OS-Copilot/FRIDAY/blob/main/LICENSE) +[](https://github.com/OS-Copilot/OS-Copilot/blob/main/LICENSE)  [](https://github.com/OS-Copilot/FRIDAY-front) @@ -38,30 +39,30 @@ 1. **Clone the GitHub Repository:** ``` - git clone https://github.com/OS-Copilot/FRIDAY.git + git clone https://github.com/OS-Copilot/OS-Copilot.git ``` 2. **Set Up Python Environment:** Ensure you have a version 3.10 or higher Python environment. You can create and - activate this environment using the following commands, replacing `FRIDAY_env` with your preferred environment + activate this environment using the following commands, replacing `oscopilot_env` with your preferred environment name: ``` - conda create -n FRIDAY_env python=3.10 -y - conda activate FRIDAY_env + conda create -n oscopilot_env python=3.10 -y + conda activate oscopilot_env ``` -3. **Install Dependencies:** Move into the `FRIDAY` directory and install the necessary dependencies by running: +3. **Install Dependencies:** Move into the `OS-Copilot` directory and install the necessary dependencies by running: ``` - cd FRIDAY - pip install -r requirements.txt + cd OS-Copilot + pip install -e . ``` 4. **Set OpenAI API Key:** Configure your OpenAI API key in [.env](.env) and select the model you wish to use. -5. **Execute Your Task:** Run the following command to start FRIDAY. Replace `[query]` with your task as needed. By default, the task is *"Move the text files containing the word 'agent' from the folder named 'document' to the path 'working_dir/agent'"*. If the task requires using related files, you can use `--query_file_path [file_path]`. +5. **Running the Script:** Run the quick_start.py script, simply execute the following command in your terminal: ``` - python run.py --query [query] + python quick_start.py ``` \* FRIDAY currently only supports single-round conversation. @@ -73,219 +74,43 @@ For a detailed list of tools, please see [FRIDAY-Gizmos](https://github.com/OS-C 1. Find the tool you want to use in [FRIDAY-Gizmos](https://github.com/OS-Copilot/FRIDAY-Gizmos) and download its tool code. 2. Add the tool to FRIDAY's toolkit: ```shell -python friday/core/action_manager.py --add --tool_name [tool_name] --tool_path [tool_path] +python friday/tool_repository/manager/tool_manager.py --add --tool_name [tool_name] --tool_path [tool_path] ``` 3. If you wish to remove a tool, you can run: ```shell -python friday/core/action_manager.py --delete --tool_name [tool_name] +python friday/tool_repository/manager/tool_manager.py --delete --tool_name [tool_name] ``` ## 💻 User Interface (UI) **Enhance Your Experience with Our Intuitive Frontend!** This interface is crafted for effortless control of your agents. For more details, visit [FRIDAY Frontend](https://github.com/OS-Copilot/FRIDAY-front). -## ✨ Deploy your own API tools with FastAPI -All FastAPIs are under: [friday/api](friday/api) -1. **Prepare your FastAPI file:** Create a new api folder under [friday/api](friday/api) and put your FastAPi python files under that folder. -2. **Import your FastAPI in API server:** Import your apis in [friday/core/api_server.py](friday/core/api_server.py): -```python -import os +## ✨ Deploy API Services -from fastapi import FastAPI -from friday.core.server_config import ConfigManager - -app = FastAPI() +For comprehensive guidelines on deploying API services, please refer to the [OS-Copilot documentation](). -from friday.api.bing.bing_service import router as bing_router -#[TODO] Import your own api here +## 👨💻 Contributors - -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.requests import Request - - -class LoggingMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - print(f"Incoming request: {request.method} {request.url}") - try: - response = await call_next(request) - except Exception as e: - print(f"Request error: {str(e)}") - raise e from None - else: - print(f"Outgoing response: {response.status_code}") - return response - - -app.add_middleware(LoggingMiddleware) - -# Create a dictionary that maps service names to their routers -services = { - "bing": bing_router, - # [TODO] Add your api router here - -} - -server_list = [ - "bing", - # [TODO] Add your api's service name here. -] - -# Include only the routers for the services listed in server_list -for service in server_list: - if service in services: - app.include_router(services[service]) - -# proxy_manager = ConfigManager() -# proxy_manager.apply_proxies() - -if __name__ == "__main__": - import uvicorn - # you can change your port anyway - uvicorn.run(app, host="0.0.0.0", port=8079) -``` -3. **Run API server:** -Run the server in localhost,or deploy it on your web server: -``` -python api_server.py -``` -4. **Update API documentation:** - -Update the API documentation located in [friday/core/openapi.json](friday/core/openapi.json). After launching the API server, you can access the current OpenAPI documentation at `http://localhost:8079/openapi.json`. - -Ensure to thoroughly update each API's summary in the documentation to clearly explain its functionality and usage. This is crucial as FRIDAY relies on these descriptions to understand the purpose of each API. - -For example: -```json -{ - "openapi": "3.1.0", - "info": { - "title": "FastAPI", - "version": "0.1.0" - }, - "paths": { - "/tools/audio2text": { - "post": { - // [TODO] change the summary to describe the usage of your api. - "summary": "A tool that converts audio to natural language text", - "operationId": "audio2text_tools_audio2text_post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_audio2text_tools_audio2text_post" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - - }, - "components": { - "schemas": { - "Body_audio2text_tools_audio2text_post": { - "properties": { - "file": { - "type": "string", - "format": "binary", - "title": "File" - } - }, - "type": "object", - "required": [ - "file" - ], - "title": "Body_audio2text_tools_audio2text_post" - }, - - - } - } -} -``` - -5. **Change the base url of tool_request_util.py:** FRIDAY utilizes the script located at [friday/core/tool_request_util.py](friday/core/tool_request_util.py) to interface with your API tools. After deploying your APIs, make sure to update the base URL in this file to match your API server's URL. -```python -import requests -class ToolRequestUtil: - def __init__(self): - self.session = requests.session() - self.headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML like Gecko) Chrome/52.0.2743.116 Safari/537.36'} - # [TODO] Change the base url - self.base_url = "http://localhost:8079" - - def request(self, api_path, method, params=None, files=None, content_type="application/json"): - """ - :param api_path: the path of the api - :param method: get/post - :param params: the params of the api, can be None - :param files: files to be uploaded, can be None - :param content_type: the content_type of api, e.g., application/json, multipart/form-data, can be None - :return: the return of the api - """ - url = self.base_url + api_path - try: - if method.lower() == "get": - if content_type == "application/json": - result = self.session.get(url=url, json=params, headers=self.headers, timeout=60).json() - else: - result = self.session.get(url=url, params=params, headers=self.headers, timeout=60).json() - elif method.lower() == "post": - if content_type == "multipart/form-data": - result = self.session.post(url=url, files=files, data=params, headers=self.headers).json() - elif content_type == "application/json": - result = self.session.post(url=url, json=params, headers=self.headers).json() - else: - result = self.session.post(url=url, data=params, headers=self.headers).json() - else: - print("request method error!") - return None - return result - except Exception as e: - print("http request error: %s" % e) - return None -``` - + + +## 🏫 Community + +Join our community to connect with other enthusiasts, share your tools and demos, and collaborate on innovative projects. Stay engaged and get the latest updates by following us: + +- **Discord**: Join our Discord server for real-time discussions, support, and to share your work with the community. Click here to join: [Discord Server](https://discord.gg/PDsRrEV27b). +- **Twitter**: Follow us on Twitter [@oscopilot](https://twitter.com/oscopilot) for the latest news, updates, and highlights from our community. + ## 🛡 Disclaimer OS-Copilot is provided "as is" without warranty of any kind. Users assume full responsibility for any risks associated with its use, including **potential data loss** or **changes to system settings**. The developers of OS-Copilot are not liable for any damages or losses resulting from its use. Users must ensure their actions comply with applicable laws and regulations. -## 🏫 Community - -Join our community to connect with other agent enthusiasts, share your tools and demos, and collaborate on exciting initiatives. You can find us on [Slack](https://join.slack.com/t/os-copilot/shared_invite/zt-2cqebow90-soac9UFKGZ2RcUy8PqjZrA). - - ## 🔎 Citation ``` diff --git a/config.json b/config.json deleted file mode 100644 index 3ee092d..0000000 --- a/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "model_name": "gpt-4-1106-preview", - "OPENAI_API_KEY": "", - "OPENAI_ORGANIZATION": "" -} \ No newline at end of file diff --git a/course_learning.py b/course_learning.py new file mode 100644 index 0000000..49f63f6 --- /dev/null +++ b/course_learning.py @@ -0,0 +1,12 @@ +from oscopilot import FridayAgent, FridayExecutor, FridayPlanner, FridayRetriever, SelfLearner, SelfLearning, ToolManager, TextExtractor +from oscopilot.utils import setup_config + + +args = setup_config() +software_name = args.software_name +package_name = args.package_name +demo_file_path = args.demo_file_path + +friday_agent = FridayAgent(FridayPlanner, FridayRetriever, FridayExecutor, ToolManager, config=args) +self_learning = SelfLearning(friday_agent, SelfLearner, ToolManager, args, TextExtractor) +self_learning.self_learning(software_name, package_name, demo_file_path) \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/FRIDAY.png b/docs/source/_static/FRIDAY.png new file mode 100644 index 0000000..33f44ef Binary files /dev/null and b/docs/source/_static/FRIDAY.png differ diff --git a/docs/source/_static/demo.png b/docs/source/_static/demo.png new file mode 100644 index 0000000..c5d8176 Binary files /dev/null and b/docs/source/_static/demo.png differ diff --git a/docs/source/_static/demo_openapi.png b/docs/source/_static/demo_openapi.png new file mode 100644 index 0000000..73df928 Binary files /dev/null and b/docs/source/_static/demo_openapi.png differ diff --git a/docs/source/_static/framework.png b/docs/source/_static/framework.png new file mode 100644 index 0000000..5522611 Binary files /dev/null and b/docs/source/_static/framework.png differ diff --git a/docs/source/_templates/introduction.md b/docs/source/_templates/introduction.md new file mode 100644 index 0000000..0ee0e40 --- /dev/null +++ b/docs/source/_templates/introduction.md @@ -0,0 +1,38 @@ +Introduction +================================== + +
+
+
+
+
+
+
**************************")
- print(code)
- print("*************************************************")
- state = self.environment.step(code)
- print("**************************************************")
- # print(code)
- # print("*************************************************")
- # state = self.environment.step(code)
- # print("************************
- """
- subprocess.run(["osascript", "-e", applescript])
-
-task_name()
-'''
-
-_LINUX_SYSTEM_PROMPT = '''
-You are a helpful assistant that writes Python code to complete any task specified by me.
-I will give you the following informations:
-System Version: {system_version}
-Task: {task}
-You should only respond in the format as described below:
-
-from friday.action.base_action import BaseAction
-
-# TODO: you should write a class in the following format, and the class name should be the same as the task name,besides,you can design the parameters of __call__ as you want.
-class task_name(BaseAction):
- def __init__(self) -> None:
- super().__init__()
- # self._description should be initialized as the description of the task
- self._description = ""
- # self.action_type should be initialized as the type of the task, which can be 'BASH' or 'PYTHON'
- self.action_type = ''
-
- def __call__(self, *args):
- # TODO: write your code here
-
-
-
-'''
-
-
-class SkillCreator():
- """
- SkillCreator is used to generate new skills and store them in the action_lib.
- """
- def __init__(self, config_path=None) -> None:
- super().__init__()
- self.llm = OpenAI(config_path)
- self.system_version = get_os_version()
- try:
- check_os_version(self.system_version)
- except ValueError as e:
- print(e)
- # self.mac_systom_prompts =
-
- def format_message(self, task):
- self.prompt = _MAC_SYSTEM_PROMPT.format(
- system_version=self.system_version,
- task=task
- )
- self.message = [
- {"role": "system", "content": self.prompt},
- {"role": "user", "content": task},
- ]
- return self.llm.chat(self.message)
-
-
\ No newline at end of file
diff --git a/friday/agent/tool_agent.py b/friday/agent/tool_agent.py
deleted file mode 100644
index 0c95cc7..0000000
--- a/friday/agent/tool_agent.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from friday.core.llms import OpenAI
-from friday.environment.py_env import PythonEnv
-import json
-'''
-让大模型根据目标工具的API文档做网络请求,获取到响应数据并返回
-'''
-TOOL_SYS_PROMPT='''
-You are a useful AI assistant capable of accessing APIs to complete user-specified tasks, according to API documentation,
-by using the provided ToolRequestUtil tool. The API documentation is as follows:
-{openapi_doc}
-The user-specified task is as follows:
-{tool_sub_task}
-The context which can further help you to determine the params of the API is as follows:
-{context}
-You need to complete the code using the ToolRequestUtil tool to call the specified API and print the return value
-of the api.
-ToolRequestUtil is a utility class, and the parameters of its 'request' method are described as follows:
-def request(self, api_path, method, params=None, content_type=None):
- """
- :param api_path: the path of the API
- :param method: get/post
- :param params: the parameters of the API, can be None
- :param content_type: the content type of the API, e.g., application/json, can be None
- :return: the response from the API
- """
-Please begin your code completion:
-
-'''
-
-TOOL_USER_PROMPT='''
-from friday.core.tool_request_util import ToolRequestUtil
-tool_request_util = ToolRequestUtil()
-# TODO: your code here
-'''
-
-class ToolAgent():
- ''' ToolAgent is used to call the tool api and get the result feedback '''
- def __init__(self, config_path=None, open_api_doc_path = None) -> None:
- super().__init__()
- self.llm = OpenAI(config_path)
- self.open_api_doc = {}
- self.environment = PythonEnv()
- with open(open_api_doc_path) as f:
- self.open_api_doc = json.load(f)
- # self.mac_systom_prompts =
-
- def generate_call_api_code(self, tool_sub_task,tool_api_path,context="No context provided."):
- self.sys_prompt = TOOL_SYS_PROMPT.format(
- openapi_doc = json.dumps(self.generate_openapi_doc(tool_api_path)),
- tool_sub_task = tool_sub_task,
- context = context
- )
- self.user_prompt = TOOL_USER_PROMPT
- self.message = [
- {"role": "system", "content": self.sys_prompt},
- {"role": "user", "content": self.user_prompt},
- ]
- return self.llm.chat(self.message)
- def generate_openapi_doc(self, tool_api_path):
- # init current api's doc
- curr_api_doc = {}
- curr_api_doc["openapi"] = self.open_api_doc["openapi"]
- curr_api_doc["info"] = self.open_api_doc["info"]
- curr_api_doc["paths"] = {}
- curr_api_doc["components"] = {"schemas":{}}
- api_path_doc = {}
- #extract path and schema
- if tool_api_path not in self.open_api_doc["paths"]:
- curr_api_doc = {"error": "The api is not existed"}
- return curr_api_doc
- api_path_doc = self.open_api_doc["paths"][tool_api_path]
- curr_api_doc["paths"][tool_api_path] = api_path_doc
- find_ptr = {}
- if "get" in api_path_doc:
- findptr = api_path_doc["get"]
- elif "post" in api_path_doc:
- findptr = api_path_doc["post"]
- api_params_schema_ref = ""
- # json格式
- if (("requestBody" in findptr) and
- ("content" in findptr["requestBody"]) and
- ("application/json" in findptr["requestBody"]["content"]) and
- ("schema" in findptr["requestBody"]["content"]["application/json"]) and
- ("$ref" in findptr["requestBody"]["content"]["application/json"]["schema"])):
- api_params_schema_ref = findptr["requestBody"]["content"]["application/json"]["schema"]["$ref"]
- elif (("requestBody" in findptr) and
- ("content" in findptr["requestBody"]) and
- ("multipart/form-data" in findptr["requestBody"]["content"]) and
- ("schema" in findptr["requestBody"]["content"]["multipart/form-data"]) and
- ("allOf" in findptr["requestBody"]["content"]["multipart/form-data"]["schema"]) and
- ("$ref" in findptr["requestBody"]["content"]["multipart/form-data"]["schema"]["allOf"][0])):
- api_params_schema_ref = findptr["requestBody"]["content"]["multipart/form-data"]["schema"]["allOf"][0]["$ref"]
- if api_params_schema_ref != None and api_params_schema_ref != "":
- curr_api_doc["components"]["schemas"][api_params_schema_ref.split('/')[-1]] = self.open_api_doc["components"]["schemas"][api_params_schema_ref.split('/')[-1]]
- return curr_api_doc
- def extract_python_code(self, response):
- python_code = ""
- if '```python' in response:
- python_code = response.split('```python')[1].split('```')[0]
- elif '```' in python_code:
- python_code = response.split('```')[1].split('```')[0]
- return python_code
- def execute_code(self,code):
- state = self.environment.step(code)
- api_result = None
- if(state.error != None and state.error != ""):
- api_result = state.error
- else:
- api_result = state.result
- return api_result
-
-
-# agent = ToolAgent("../../examples/config.json","../core/openapi.json")
-# res = agent.generate_openapi_doc("/tools/image_caption")
-# print(res)
-# code_text = agent.generate_call_api_code("use /tools/bing/searchv2 tool to search How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.","/tools/bing/searchv2")
-# code = agent.extract_python_code(code_text)
-# print(code)
-# api_res = agent.execute_code(code)
-# print(api_res)
\ No newline at end of file
diff --git a/friday/api/arxiv/arxiv.py b/friday/api/arxiv/arxiv.py
deleted file mode 100644
index dd02328..0000000
--- a/friday/api/arxiv/arxiv.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from fastapi import APIRouter
-from pydantic import BaseModel
-import arxiv
-
-router = APIRouter()
-
-
-class ArxivQuery(BaseModel):
- query: str
-
-
-top_k_results: int = 3
-ARXIV_MAX_QUERY_LENGTH = 300
-doc_content_chars_max: int = 4000
-
-
-@router.get("/tools/arxiv")
-async def get_arxiv_article_information(item: ArxivQuery):
- '''Run Arxiv search and get the article meta information.
- '''
- try:
- results = arxiv.Search( # type: ignore
- item.query[: ARXIV_MAX_QUERY_LENGTH], max_results=top_k_results
- ).results()
- except Exception as ex:
- return {"result": None, "error": f"Arxiv exception: {ex}"}
-
- docs = [
- f"Published: {result.updated.date()}\nTitle: {result.title}\n"
- f"Authors: {', '.join(a.name for a in result.authors)}\n"
- f"Summary: {result.summary}"
- for result in results
- ]
- if docs:
- return {"result": "\n\n".join(docs)[: doc_content_chars_max], "error": None}
- else:
- return {"result": None, "error": "No good Arxiv Result was found"}
\ No newline at end of file
diff --git a/friday/api/arxiv/test.py b/friday/api/arxiv/test.py
deleted file mode 100644
index 9cfe259..0000000
--- a/friday/api/arxiv/test.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import requests
-
-response = requests.get(
- 'http://43.159.144.130:8079/tools/arxiv',
- json={'query': 'autogen'}
-)
-
-print(response.json())
\ No newline at end of file
diff --git a/friday/api/calculator/calculator.py b/friday/api/calculator/calculator.py
deleted file mode 100644
index c5647a6..0000000
--- a/friday/api/calculator/calculator.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from fastapi import APIRouter, HTTPException
-from pydantic import BaseModel
-from simpleeval import simple_eval, SimpleEval
-
-router = APIRouter()
-
-class Expression(BaseModel):
- expression: str
-
-
-@router.post("/tools/calculator")
-def evaluate(expression: Expression):
- try:
- s = SimpleEval()
- result = s.eval(expression.expression)
- return {"result": str(result), "error": None}
- except Exception as e:
- return {"result": None, "error": str(e)}
diff --git a/friday/api/calculator/test.py b/friday/api/calculator/test.py
deleted file mode 100644
index 74de469..0000000
--- a/friday/api/calculator/test.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import requests
-import json
-
-# 测试加法
-expression = "((46210 - 8*9068) / (2 - x))"
-response = requests.post(
- 'http://127.0.0.1:8079/tools/calculator',
- json={'expression': expression}
-)
-print(response.json())
\ No newline at end of file
diff --git a/friday/api/chemical/chemical.py b/friday/api/chemical/chemical.py
deleted file mode 100644
index 35fdb61..0000000
--- a/friday/api/chemical/chemical.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import random
-
-from fastapi import APIRouter
-from pydantic import BaseModel
-from typing import List, Optional, Union
-from .chemical_prop_api import ChemicalPropAPI
-
-router = APIRouter()
-
-
-class GetNameResponse(BaseModel):
- """name list"""
- names: List[str]
-
-
-class GetStructureResponse(BaseModel):
- """structure list"""
- state: int
- content: Optional[str] = None
-
-
-class GetIDResponse(BaseModel):
- state: int
- content: Union[str, List[str]]
-
-
-chemical_prop_api = ChemicalPropAPI
-
-
-@router.get("/tools/chemical/get_name", response_model=GetNameResponse)
-def get_name(cid: str):
- """prints the possible 3 synonyms of the queried compound ID"""
- ans = chemical_prop_api.get_name_by_cid(cid, top_k=3)
- return {
- "names": ans
- }
-
-
-@router.get("/tools/chemical/get_allname", response_model=GetNameResponse)
-def get_allname(cid: str):
- """prints all the possible synonyms (might be too many, use this function carefully).
- """
- ans = chemical_prop_api.get_name_by_cid(cid)
- return {
- "names": ans
- }
-
-
-@router.get("/tools/chemical/get_id_by_struct", response_model=GetIDResponse)
-def get_id_by_struct(smiles: str):
- """prints the ID of the queried compound SMILES. This should only be used if smiles is provided or retrieved in the previous step. The input should not be a string, but a SMILES formula.
- """
- cids = chemical_prop_api.get_cid_by_struct(smiles)
- if len(cids) == 0:
- return {
- "state": "no result"
- }
- else:
- return {
- "state": "matched",
- "content": cids[0]
- }
-
-
-@router.get("/tools/chemical/get_id", response_model=GetIDResponse)
-def get_id(name: str):
- """prints the ID of the queried compound name, and prints the possible 5 names if the queried name can not been precisely matched,
- """
- cids = chemical_prop_api.get_cid_by_name(name)
- if len(cids) > 0:
- return {
- "state": "precise",
- "content": cids[0]
- }
-
- cids = chemical_prop_api.get_cid_by_name(name, name_type="word")
- if len(cids) > 0:
- if name in get_name(cids[0]):
- return {
- "state": "precise",
- "content": cids[0]
- }
-
- ans = []
- random.shuffle(cids)
- for cid in cids[:5]:
- nms = get_name(cid)
- ans.append(nms)
- return {
- "state": "not precise",
- "content": ans
- }
-
-
-@router.get("/tools/chemical/get_prop")
-def get_prop(cid: str):
- """prints the properties of the queried compound ID
- """
- return chemical_prop_api.get_prop_by_cid(cid)
diff --git a/friday/api/chemical/chemical_prop_api.py b/friday/api/chemical/chemical_prop_api.py
deleted file mode 100644
index 363803f..0000000
--- a/friday/api/chemical/chemical_prop_api.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import json
-from typing import Optional, List
-
-import requests
-from bs4 import BeautifulSoup
-
-
-class ChemicalPropAPI:
- def __init__(self) -> None:
- self._endpoint = "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/"
-
- def get_name_by_cid(self, cid: str, top_k: Optional[int] = None) -> List[str]:
- html_doc = requests.get(f"{self._endpoint}cid/{cid}/synonyms/XML").text
- soup = BeautifulSoup(html_doc, "html.parser", from_encoding="utf-8")
- syns = soup.find_all('synonym')
- ans = []
- if top_k is None:
- top_k = len(syns)
- for syn in syns[:top_k]:
- ans.append(syn.text)
- return ans
-
- def get_cid_by_struct(self, smiles: str) -> List[str]:
- html_doc = requests.get(f"{self._endpoint}smiles/{smiles}/cids/XML").text
- soup = BeautifulSoup(html_doc, "html.parser", from_encoding="utf-8")
- cids = soup.find_all('cid')
- if cids is None:
- return []
- ans = []
- for cid in cids:
- ans.append(cid.text)
- return ans
-
- def get_cid_by_name(self, name: str, name_type: Optional[str] = None) -> List[str]:
- url = f"{self._endpoint}name/{name}/cids/XML"
- if name_type is not None:
- url += f"?name_type={name_type}"
- html_doc = requests.get(url).text
- soup = BeautifulSoup(html_doc, "html.parser", from_encoding="utf-8")
- cids = soup.find_all('cid')
- if cids is None:
- return []
- ans = []
- for cid in cids:
- ans.append(cid.text)
- return ans
-
- def get_prop_by_cid(self, cid: str) -> str:
- html_doc = requests.get(
- f"{self._endpoint}cid/{cid}/property/MolecularFormula,MolecularWeight,CanonicalSMILES,IsomericSMILES,IUPACName,XLogP,ExactMass,MonoisotopicMass,TPSA,Complexity,Charge,HBondDonorCount,HBondAcceptorCount,RotatableBondCount,HeavyAtomCount,CovalentUnitCount/json").text
- return json.loads(html_doc)['PropertyTable']['Properties'][0]
\ No newline at end of file
diff --git a/friday/api/database/database.py b/friday/api/database/database.py
deleted file mode 100644
index 881b92a..0000000
--- a/friday/api/database/database.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import datetime
-from typing import List
-from fastapi import APIRouter, HTTPException
-from pydantic import BaseModel
-import sqlite3
-
-router = APIRouter()
-
-
-class SQLRequest(BaseModel):
- queries: List[str]
-
-
-def execute_sql(queries: List[str]):
- conn = sqlite3.connect('./tasks/travel/database/travel.db')
- cursor = conn.cursor()
-
- results = []
- for query in queries:
- try:
- cursor.execute(query)
- results.append({
- "query": query,
- "result": cursor.fetchall(),
- "error": ""
- })
- except Exception as e:
- results.append({
- "query": query,
- "result": "",
- "error": str(e)
- })
-
- # Commit changes and close the connection to the database
- conn.commit()
- conn.close()
-
- return results
-
-
-@router.post("/tools/database")
-async def execute_sqlite(req: SQLRequest):
- print(f"{datetime.datetime.now()}:{req}")
- return execute_sql(req.queries)
diff --git a/friday/api/database/test.py b/friday/api/database/test.py
deleted file mode 100644
index 7a01728..0000000
--- a/friday/api/database/test.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import requests
-import json
-
-# 你的API的URL
-url = "http://localhost:8079/tools/database"
-
-# 准备的SQL查询
-queries = {
- "queries": [
- '''SELECT * FROM railway
-WHERE origin = 'Beijing'
- AND destination = 'Shanghai'
- AND DATE(departure_time) = '2023-07-08'
-ORDER BY departure_time;'''
- ]
-}
-# queries = {"queries":["SELECT * FROM railway\nWHERE origin = 'Shanghai'\n AND destination = 'Hangzhou'\n AND DATE(departure_time) = '2023-07-04';"]
-# }
-
-# 发送POST请求
-response = requests.post(url, json=queries)
-
-# 打印返回的结果
-print(json.dumps(response.json(), indent=4))
-
-
-def query_database(query):
- try:
- response = requests.post(
- "http://localhost:8079/tools/database",
- json={'queries': query}
- ).json()
- return json.dumps(response, indent=4)
- except Exception as e:
- print(f'run error{e}')
-
-
-query = [
- "SELECT * FROM railway\nWHERE origin = 'Shanghai'\n AND destination = 'Beijing'\n AND DATE(departure_time) = '2023-07-01';",
- "SELECT * FROM railway\nWHERE origin = 'Beijing'\n AND destination = 'Hangzhou'\n AND DATE(departure_time) = '2023-07-04';",
- "SELECT * FROM railway\nWHERE origin = 'Hangzhou'\n AND destination = 'Shanghai'\n AND DATE(departure_time) = '2023-07-07';"]
-print(query_database(query))
diff --git a/friday/api/gmail/gmail.py b/friday/api/gmail/gmail.py
deleted file mode 100644
index b96eb13..0000000
--- a/friday/api/gmail/gmail.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import os
-from google.oauth2.credentials import Credentials
-from google_auth_oauthlib.flow import InstalledAppFlow
-from google.auth.transport.requests import Request as google_request
-from googleapiclient.discovery import build
-import base64
-from email.mime.text import MIMEText
-import pickle
-
-SCOPES = ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.readonly',
- 'https://www.googleapis.com/auth/calendar']
-
-
-def get_service():
- creds = None
- # 尝试从 "token.pickle" 文件中加载凭据
- if os.path.exists('token.pickle'):
- with open('token.pickle', 'rb') as token:
- creds = pickle.load(token)
- if creds and not creds.valid:
- if creds.expired and creds.refresh_token:
- creds.refresh(google_request())
- else:
- flow = InstalledAppFlow.from_client_secrets_file(
- './.auth/calendar.json', SCOPES)
- creds = flow.run_local_server(port=0)
- service = build('gmail', 'v1', credentials=creds)
- return service
-
-
-def send_email(service, from_email, to_email, subject, content):
- message = MIMEText(content)
- message['to'] = to_email
- message['from'] = from_email
- message['subject'] = subject
- raw_message = base64.urlsafe_b64encode(message.as_string().encode("utf-8"))
- message = {'raw': raw_message.decode("utf-8")}
- message = (service.users().messages().send(userId='me', body=message).execute())
-
-
-from fastapi import APIRouter
-import json
-import base64
-from email.mime.text import MIMEText
-from pydantic import BaseModel
-
-router = APIRouter()
-
-
-class EmailSchema(BaseModel):
- from_email: str
- to_email: str
- subject: str
- content: str
-
-
-@router.post("/gmail/send")
-def send_test_email(email: EmailSchema):
- try:
- service = get_service()
- send_email(service, email.from_email, email.to_email, email.subject, email.content) # 注意这里从email对象中取字段
- return {"result": "Email sent successfully", "error": None}
- except Exception as e:
- return {"result": None, "error": str(e)}
-
-
-# token.pickle文件包含了与特定Gmail账户关联的访问令牌
-@router.get("/gmail/list")
-def list_recent_emails():
- try:
- service = get_service()
- results = service.users().messages().list(userId='me', labelIds=['INBOX'], maxResults=10).execute()
- messages = results.get('messages', [])
- emails = []
- for message in messages:
- msg = service.users().messages().get(userId='me', id=message['id']).execute()
- email_data = msg['payload']['headers']
- for values in email_data:
- name = values['name']
- if name == 'From':
- from_name = values['value']
- subject = msg['snippet']
- emails.append({"from": from_name, "subject": subject})
- return {"emails": emails, "error": None}
- except Exception as e:
- return {"emails": None, "error": str(e)}
diff --git a/friday/api/gmail/test.py b/friday/api/gmail/test.py
deleted file mode 100644
index 9200278..0000000
--- a/friday/api/gmail/test.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os
-
-import requests
-import json
-
-# 基础URL
-BASE_URL = "http://127.0.0.1:8079"
-
-# 测试邮件发送API
-def test_send_email():
- print("Testing: Send Email API")
-
- data = {
- "from_email": "wyx7653@gmail.com",
- "to_email": "2115492705@qq.com",
- "subject": "Test Subject",
- "content": "This is a test email."
- }
-
- response = requests.post(f"{BASE_URL}/gmail/send", json=data)
- if response.status_code == 200:
- print(f"Success: {response.json()}")
- else:
- print(f"Failure: {response.json()}")
-
-# 测试获取最近邮件列表API
-def test_list_recent_emails():
- print("Testing: List Recent Emails API")
-
- response = requests.get(f"{BASE_URL}/gmail/list")
- if response.status_code == 200:
- print(f"Success: {response.json()}")
- else:
- print(f"Failure: {response.json()}")
-
-if __name__ == "__main__":
- test_send_email()
- test_list_recent_emails()
\ No newline at end of file
diff --git a/friday/api/google_calendar/calendar_service.py b/friday/api/google_calendar/calendar_service.py
deleted file mode 100644
index 4534211..0000000
--- a/friday/api/google_calendar/calendar_service.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# 开启Google Calendar API并下载凭据: 访问Google Cloud Console,创建一个新的项目并启用Google Calendar API。下载生成的credentials.json文件。
-import os
-
-from fastapi import APIRouter
-from pydantic import BaseModel, Field
-from google_auth_oauthlib.flow import InstalledAppFlow
-from google.auth.transport.requests import Request as google_request
-from googleapiclient.discovery import build
-import pickle
-
-# 如果修改了SCOPES,请删除文件token.pickle。
-SCOPES = ['https://www.googleapis.com/auth/gmail.send','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/calendar']
-
-
-
-def get_service():
- creds = None
- # 尝试从 "token.pickle" 文件中加载凭据
- if os.path.exists('token.pickle'):
- with open('token.pickle', 'rb') as token:
- creds = pickle.load(token)
-
- # 如果凭据无效,重新获取
- if not creds or not creds.valid:
- if creds and creds.expired and creds.refresh_token:
- creds.refresh(google_request())
- else:
- flow = InstalledAppFlow.from_client_secrets_file(
- './.auth/calendar.json', SCOPES)
- creds = flow.run_local_server(port=0)
-
- # 保存新的凭据到 "token.pickle" 文件
- with open('token.pickle', 'wb') as token:
- pickle.dump(creds, token)
-
- service = build('calendar', 'v3', credentials=creds)
- return service
-router = APIRouter()
-
-
-class CalendarEvent(BaseModel):
- summary: str
- location: str
- description: str
- start: dict = Field(..., example={"dateTime": "2023-07-31T15:00:00", "timeZone": "Asia/Shanghai"})
- end: dict = Field(..., example={"dateTime": "2023-07-31T16:00:00", "timeZone": "Asia/Shanghai"})
-
-
-@router.post("/calendar/insert_event")
-def insert_event(event: CalendarEvent):
- try:
- # 这里你可以调用Google Calendar API
- service = get_service() # 从你原来的代码获取service
- inserted_event = service.events().insert(calendarId='primary', body=event.dict()).execute()
- return {"result": f'Event created: {inserted_event["htmlLink"]}', "error": None}
- except Exception as e:
- return {"result": None, "error": str(e)}
diff --git a/friday/api/google_calendar/test.py b/friday/api/google_calendar/test.py
deleted file mode 100644
index decdd41..0000000
--- a/friday/api/google_calendar/test.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import requests
-import json
-
-# 用于测试的日历事件
-test_event = {
- "summary": "NLUI会议",
- "location": "上海",
- "description": "这是一个关于NLUI的会议",
- "start": {
- "dateTime": "2023-08-28T10:30:00",
- "timeZone": "Asia/Shanghai"
- },
- "end": {
- "dateTime": "2023-08-28T11:30:00", # 假设会议时长为1小时
- "timeZone": "Asia/Shanghai"
- }
-}
-
-# 向API发送请求
-response = requests.post("http://127.0.0.1:8079/calendar/insert_event", json=test_event)
-
-# 解析响应
-if response.status_code == 200:
- data = response.json()
- print(data)
\ No newline at end of file
diff --git a/friday/api/markdown/markdown_service.py b/friday/api/markdown/markdown_service.py
deleted file mode 100644
index a12ac8b..0000000
--- a/friday/api/markdown/markdown_service.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from fastapi import APIRouter, HTTPException
-from pydantic import BaseModel,Field
-from typing import Optional
-from .webpage2md import WebPage2MDTool
-import tiktoken
-
-
-
-router = APIRouter()
-
-web2MdTool = WebPage2MDTool()
-
-class TargetPageModel(BaseModel):
- url: str
-
-
-
-
-@router.get("/tools/markdown/web2md")
-async def get_web_md(item: TargetPageModel):
- result = {"markdown": ""}
- try:
- markdown_text = web2MdTool.get_web_md(item.url)
- result["markdown"] = markdown_text
- except RuntimeError as e:
- raise HTTPException(status_code=500, detail=str(e))
- return result
\ No newline at end of file
diff --git a/friday/api/markdown/webpage2md.py b/friday/api/markdown/webpage2md.py
deleted file mode 100644
index 3e08c51..0000000
--- a/friday/api/markdown/webpage2md.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import re
-import requests
-import html2text as ht
-from urllib.parse import urljoin
-try:
- from bs4 import BeautifulSoup
-except ImportError:
- raise ImportError(
- 'Webpage requires extra dependencies. Install with `pip install --upgrade "embedchain[dataloaders]"`'
- ) from None
-
-class WebPage2MDTool:
- _session = requests.Session()
- def get_web_md(self, url):
- """Load data from a web page using a shared requests session."""
- headers = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML like Gecko) Chrome/52.0.2743.116 Safari/537.36'}
- try:
- response = self._session.get(url,headers=headers, timeout=30)
- response.raise_for_status()
- data = response.content
- content = self._get_clean_content(data, url)
- text_maker = ht.HTML2Text()
- md_text = text_maker.handle(content)
- except Exception:
- md_text = "error loading markdown of current webpage"
- return md_text
- def _get_clean_content(self, html, url) -> str:
- soup = BeautifulSoup(html, "html.parser")
- original_size = len(str(soup.get_text()))
-
- tags_to_exclude = [
- "nav",
- "aside",
- "form",
- "header",
- "noscript",
- "svg",
- "canvas",
- "footer",
- "script",
- "style",
- ]
- for tag in soup(tags_to_exclude):
- tag.decompose()
-
- ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"]
- for id in ids_to_exclude:
- tags = soup.find_all(id=id)
- for tag in tags:
- tag.decompose()
-
- classes_to_exclude = [
- "elementor-location-header",
- "navbar-header",
- "nav",
- "header-sidebar-wrapper",
- "blog-sidebar-wrapper",
- "related-posts",
- ]
- for class_name in classes_to_exclude:
- tags = soup.find_all(class_=class_name)
- for tag in tags:
- tag.decompose()
- # 将相对路径转绝对路径
- # 查找所有带有href属性的标签
- for link in soup.find_all('a', href=True):
- absolute_url = urljoin(url, link['href'])
- link['href'] = absolute_url
-
- # 查找所有带有src属性的
标签
- for img in soup.find_all('img', src=True):
- absolute_url = urljoin(url, img['src'])
- img['src'] = absolute_url
- content = str(soup)
-
- return content
-
- @classmethod
- def close_session(cls):
- cls._session.close()
-
-# res = WebPage2MDTool().get_web_md("https://lividwo.github.io/zywu.github.io/")
-# print(type(res))
diff --git a/friday/api/ppt/ppt.py b/friday/api/ppt/ppt.py
deleted file mode 100644
index 8ff10b1..0000000
--- a/friday/api/ppt/ppt.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from fastapi import APIRouter, Depends
-from pydantic import BaseModel
-from pptx import Presentation
-import os
-import time
-import requests
-
-router = APIRouter()
-
-CWD = os.getcwd() # path of current working directory
-LIB_DIR = os.path.dirname(__file__) # path of library
-TEMPLATE_DIR = os.path.join(LIB_DIR, "templates") # path of templates
-CACHE_DIR = os.path.join(CWD, "cache") # path of cache_dir
-IMAGE_BED_PATTERN = 'https://source.unsplash.com/featured/?{}' # url pattern for image bed
-
-if not os.path.exists(CACHE_DIR):
- os.makedirs(CACHE_DIR)
-
-ppt_file = None # a pointer to the powerpoint object
-
-
-class CreateFileModel(BaseModel):
- theme: str
-
-
-class GetImageModel(BaseModel):
- keywords: str
-
-
-class AddFirstPageModel(BaseModel):
- title: str
- subtitle: str
-
-
-class AddTextPageModel(BaseModel):
- title: str
- bullet_items: str
-
-
-class AddTextImagePageModel(BaseModel):
- title: str
- bullet_items: str
- image: str
-
-
-@router.post("/tools/ppt/create_file")
-async def create_file(item: CreateFileModel):
- global ppt_file
- ppt_file = Presentation(os.path.join(TEMPLATE_DIR, f"{item.theme}.pptx"))
- return "created a ppt file."
-
-
-@router.post("/tools/ppt/get_image")
-async def get_image(item: GetImageModel):
- picture_url = IMAGE_BED_PATTERN.format(item.keywords)
- response = requests.get(picture_url)
- img_local_path = os.path.join(CACHE_DIR, f"{time.time()}.jpg")
- with open(img_local_path, 'wb') as f:
- f.write(response.content)
- return img_local_path
-
-
-@router.post("/tools/ppt/add_first_page")
-async def add_first_page(item: AddFirstPageModel):
- global ppt_file
- slide = ppt_file.slides.add_slide(ppt_file.slide_layouts[0]) # layout for first page (title and subtitle only)
- title_shape = slide.shapes.title
- subtitle_shape = slide.placeholders[1]
- title_shape.text = item.title
- subtitle_shape.text = item.subtitle
- return "added first page."
-
-
-@router.post("/tools/ppt/add_text_page")
-async def add_text_page(item: AddTextPageModel):
- global ppt_file
- slide = ppt_file.slides.add_slide(ppt_file.slide_layouts[1])
- title_shape = slide.shapes.title
- body_shape = slide.placeholders[1]
- title_shape.text = item.title
- tf = body_shape.text_frame
- bullet_items = item.bullet_items.split("[SPAN]")
- for bullet_item in bullet_items:
- bullet_item_strip = bullet_item.strip()
- p = tf.add_paragraph()
- p.text = bullet_item_strip
- p.level = 1
- return "added text page."
-
-
-@router.post("/tools/ppt/add_text_image_page")
-async def add_text_image_page(item: AddTextImagePageModel):
- global ppt_file
- slide = ppt_file.slides.add_slide(ppt_file.slide_layouts[3])
- title_shape = slide.shapes.title
- title_shape.text = item.title
- body_shape = slide.placeholders[1]
- tf = body_shape.text_frame
- bullet_items = item.bullet_items.split("[SPAN]")
- for bullet_item in bullet_items:
- bullet_item_strip = bullet_item.strip()
- p = tf.add_paragraph()
- p.text = bullet_item_strip
- p.level = 1
- image_shape = slide.placeholders[2]
- slide.shapes.add_picture(item.image, image_shape.left, image_shape.top, image_shape.width, image_shape.height)
- return "added text and image page."
-
-
-@router.get("/tools/ppt/submit_file")
-async def submit_file():
- global ppt_file
- file_path = os.path.join(CACHE_DIR, f"{time.time()}.pptx")
- ppt_file.save(file_path)
- return f"submitted. view ppt at {file_path}"
diff --git a/friday/api/ppt/templates/flat.pptx b/friday/api/ppt/templates/flat.pptx
deleted file mode 100644
index 8a6260a..0000000
Binary files a/friday/api/ppt/templates/flat.pptx and /dev/null differ
diff --git a/friday/api/ppt/templates/green.pptx b/friday/api/ppt/templates/green.pptx
deleted file mode 100644
index e63213c..0000000
Binary files a/friday/api/ppt/templates/green.pptx and /dev/null differ
diff --git a/friday/api/ppt/templates/orange.pptx b/friday/api/ppt/templates/orange.pptx
deleted file mode 100644
index 3ca6f86..0000000
Binary files a/friday/api/ppt/templates/orange.pptx and /dev/null differ
diff --git a/friday/api/ppt/templates/tech.pptx b/friday/api/ppt/templates/tech.pptx
deleted file mode 100644
index f84cab3..0000000
Binary files a/friday/api/ppt/templates/tech.pptx and /dev/null differ
diff --git a/friday/api/ppt/templates/wooden.pptx b/friday/api/ppt/templates/wooden.pptx
deleted file mode 100644
index 6e004ac..0000000
Binary files a/friday/api/ppt/templates/wooden.pptx and /dev/null differ
diff --git a/friday/api/ppt/test.py b/friday/api/ppt/test.py
deleted file mode 100644
index 7141277..0000000
--- a/friday/api/ppt/test.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import requests
-
-ppt_url='http://localhost:8079/tools/ppt'
-
-#创建文件
-requests.post(f'{ppt_url}/create_file', json={"theme": "tech"})
-#获取图片
-response = requests.post(f'{ppt_url}/get_image', json={"keywords": "programming"})
-image_path = response.json()
-#加一页
-requests.post(f'{ppt_url}/add_first_page', json={"title": "About Me", "subtitle": "A brief introduction"})
-requests.post(f'{ppt_url}/add_text_page', json={"title": "Education", "bullet_items": "Bachelor's Degree in Computer Science[SPAN]Master's Degree in Data Science"})
-requests.post(f'{ppt_url}/add_text_image_page', json={"title": "Skills", "bullet_items": "Programming[SPAN]Data Analysis[SPAN]Machine Learning", "image": image_path})
-response = requests.get(f'{ppt_url}/submit_file')
-file_path = response.json()
-print(file_path)
\ No newline at end of file
diff --git a/friday/api/python/__init__.py b/friday/api/python/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/api/python/interpreter.py b/friday/api/python/interpreter.py
deleted file mode 100644
index c405a14..0000000
--- a/friday/api/python/interpreter.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import ast
-import os
-import asyncio
-import subprocess
-
-import astor
-from fastapi import APIRouter, HTTPException
-from pydantic import BaseModel
-
-router = APIRouter()
-
-
-class Item(BaseModel):
- code: str
-
-
-def modify_code_to_print_last_expr(code: str):
- # Parse the code using AST
- tree = ast.parse(code, mode='exec')
-
- # Check if the last node is an expression and not a print statement
- last_node = tree.body[-1]
- if isinstance(last_node, ast.Expr) and not (
- isinstance(last_node.value, ast.Call) and getattr(last_node.value.func, 'id', None) == 'print'):
- # Create a new print node
- print_node = ast.Expr(
- value=ast.Call(func=ast.Name(id='print', ctx=ast.Load()), args=[last_node.value], keywords=[]))
- # Copy line number and column offset from the last expression
- print_node.lineno = last_node.lineno
- print_node.col_offset = last_node.col_offset
- # Replace the last expression with the print statement
- tree.body[-1] = print_node
-
- # Use astor to convert the modified AST back to source code
- modified_code = astor.to_source(tree)
- return modified_code
-
-
-async def run_code(code: str):
-
- try:
- code = modify_code_to_print_last_expr(code)
- # Write the code to a file
- with open("code.py", "w") as f:
- f.write(code)
- with open("code_temp.py", "w") as f:
- f.write(code)
- # Run the file with a timeout of 3 seconds
- process = await asyncio.create_subprocess_shell(
- "python code.py",
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
- stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=3)
-
- # Decode the stdout and stderr
- result = stdout.decode("utf-8")
- error = stderr.decode("utf-8")
-
- return {"result": result, "error": error}
- except asyncio.TimeoutError:
- process.terminate()
- await process.wait()
- return {"result": "", "error": "Code execution timed out"}
- except Exception as e:
- return {"result": "", "error": str(e)}
- finally:
- # Delete the code file
- if os.path.exists("code.py"):
- os.remove("code.py")
-
-
-@router.post("/tools/python")
-async def execute_python(item: Item):
- result = await run_code(item.code)
- return result
-
-# import io
-# import traceback
-# from contextlib import redirect_stdout
-# from fastapi import APIRouter, HTTPException
-# from pydantic import BaseModel
-# from concurrent.futures import ThreadPoolExecutor, TimeoutError
-#
-# router = APIRouter()
-#
-# executor = ThreadPoolExecutor(max_workers=1)
-#
-# class Item(BaseModel):
-# code: str
-#
-# def execute_code(code):
-# f = io.StringIO()
-# with redirect_stdout(f):
-# try:
-# exec(code)
-# return {"result": f.getvalue(), "error": None}
-# except Exception as e:
-# return {"result": f.getvalue(), "error": traceback.format_exc().split('exec(code)\n ')[-1]}
-#
-# @router.post("/tools/python")
-# async def execute_python(item: Item):
-# future = executor.submit(execute_code, item.code)
-# try:
-# result = future.result(timeout=3) # Wait for the result or timeout after 3 seconds
-# except TimeoutError:
-# return {"result": None, "error": "TimeoutError"}
-# return result
diff --git a/friday/api/python/temp.py b/friday/api/python/temp.py
deleted file mode 100644
index ed0f110..0000000
--- a/friday/api/python/temp.py
+++ /dev/null
@@ -1 +0,0 @@
-print('hello')
\ No newline at end of file
diff --git a/friday/api/python/test.py b/friday/api/python/test.py
deleted file mode 100644
index 460e96a..0000000
--- a/friday/api/python/test.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import requests
-import json
-
-code = """
-import heapq as hq\r\nfrom collections import Counter\r\n\r\ndef func(lists, k):\r\n nums = []\r\n for lst in lists:\r\n nums.extend(lst)\r\n count = Counter(nums)\r\n top_k = hq.nlargest(k, count, key=count.get)\r\n return top_k\nassert func([[1, 2, 6], [1, 3, 4, 5, 7, 8], [1, 3, 5, 6, 8, 9], [2, 5, 7, 11], [1, 4, 7, 8, 12]],3)==[5, 7, 1]\nassert func([[1, 2, 6], [1, 3, 4, 5, 7, 8], [1, 3, 5, 6, 8, 9], [2, 5, 7, 11], [1, 4, 7, 8, 12]],1)==[1]\nassert func([[1, 2, 6], [1, 3, 4, 5, 7, 8], [1, 3, 5, 6, 8, 9], [2, 5, 7, 11], [1, 4, 7, 8, 12]],5)==[6, 5, 7, 8, 1]\n
-"""
-code="""
-print('hello world')"""
-response = requests.post(
- 'http://127.0.0.1:8079/tools/python',
- json={'code': code}
-)
-
-print(response.json())
\ No newline at end of file
diff --git a/friday/api/python/test2.py b/friday/api/python/test2.py
deleted file mode 100644
index 842e262..0000000
--- a/friday/api/python/test2.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import requests
-import json
-
-code = """
-import heapq as hq\r\nfrom collections import Counter\r\n\r\ndef func(lists, k):\r\n nums = []\r\n for lst in lists:\r\n nums.extend(lst)\r\n count = Counter(nums)\r\n top_k = hq.nlargest(k, count, key=count.get)\r\n return top_k\nassert func([[1, 2, 6], [1, 3, 4, 5, 7, 8], [1, 3, 5, 6, 8, 9], [2, 5, 7, 11], [1, 4, 7, 8, 12]],3)==[5, 7, 1]\nassert func([[1, 2, 6], [1, 3, 4, 5, 7, 8], [1, 3, 5, 6, 8, 9], [2, 5, 7, 11], [1, 4, 7, 8, 12]],1)==[1]\nassert func([[1, 2, 6], [1, 3, 4, 5, 7, 8], [1, 3, 5, 6, 8, 9], [2, 5, 7, 11], [1, 4, 7, 8, 12]],5)==[6, 5, 7, 8, 1]\n
-"""
-code="""
-abcde
-fs"""
-response = requests.post(
- 'http://127.0.0.1:8079/tools/python',
- json={'code': code}
-)
-
-print(response.json())
\ No newline at end of file
diff --git a/friday/api/shell/__init__.py b/friday/api/shell/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/api/shell/shell.py b/friday/api/shell/shell.py
deleted file mode 100644
index 1a9c3fb..0000000
--- a/friday/api/shell/shell.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from fastapi import APIRouter
-from pydantic import BaseModel
-import subprocess
-
-router = APIRouter()
-
-class ShellCommandModel(BaseModel):
- command: str
-
-class ShellCommandResultModel(BaseModel):
- stdout: str
- stderr: str
-
-@router.post("/tools/shell", response_model=ShellCommandResultModel)
-async def execute_shell_command(command: ShellCommandModel):
- result = subprocess.run(command.command, capture_output=True, shell=True, text=True)
- return ShellCommandResultModel(stdout=result.stdout, stderr=result.stderr)
diff --git a/friday/api/shell/test.py b/friday/api/shell/test.py
deleted file mode 100644
index 25de953..0000000
--- a/friday/api/shell/test.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import requests
-import json
-
-base_url = 'http://localhost:8079'
-
-def run_shell_command(command):
- response = requests.post(f'{base_url}/tools/shell', data=json.dumps({"command": command}), headers={'Content-Type': 'application/json'})
- if response.status_code == 200:
- print("Command executed successfully")
- print("STDOUT: ", response.json()['stdout'])
- print("STDERR: ", response.json()['stderr'])
- else:
- print("Error occurred while executing the command")
-
-# Create the file in /root directory
-run_shell_command("echo 'This is a test file.' > /root/test.txt")
-
-# Copy the file to the current directory
-run_shell_command("cp /root/test.txt ./test2.txt")
diff --git a/friday/api/sympy/__init__.py b/friday/api/sympy/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/api/sympy/test.py b/friday/api/sympy/test.py
deleted file mode 100644
index 8a41c53..0000000
--- a/friday/api/sympy/test.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from sympy import symbols, Eq, solve
-
-x, y = symbols('x y')
-
-# 设方程组为:
-# 3x + 2y = 2
-# x + 2y = 0
-eq1 = Eq(3*x + 2*y, 2)
-eq2 = Eq(x + 2*y, 0)
-
-# 使用 solve 解方程组
-sol = solve((eq1,eq2), (x, y))
-print(sol)
\ No newline at end of file
diff --git a/friday/api/translate/__init__.py b/friday/api/translate/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/api/translate/translate.py b/friday/api/translate/translate.py
deleted file mode 100644
index 10d02ba..0000000
--- a/friday/api/translate/translate.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from fastapi import APIRouter
-from pydantic import BaseModel
-
-router = APIRouter()
-
-class TranslateRequest(BaseModel):
- text: str
- src_language: str
- dest_language: str
-
-class TranslateResponse(BaseModel):
- translated_text: str
-
-def translate_text(text: str, src_language: str, dest_language: str) -> str:
- """
- Translates the text from source language to destination language.
- This function is just a placeholder. You should implement the actual translation here.
- """
- # TODO: implement the translation
- return text
-
-@router.post("/tools/translate", response_model=TranslateResponse)
-async def translate(request: TranslateRequest) -> TranslateResponse:
- translated_text = translate_text(request.text, request.src_language, request.dest_language)
- return TranslateResponse(translated_text=translated_text)
\ No newline at end of file
diff --git a/friday/api/weather/__init__.py b/friday/api/weather/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/api/weather/test.py b/friday/api/weather/test.py
deleted file mode 100644
index 3c09b53..0000000
--- a/friday/api/weather/test.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# test_weather_api.py
-
-import requests
-
-def test_query_weather():
- base_url = "http://127.0.0.1:8079"
- date = "2023-07-01"
- city = "Beijing"
-
- # 发送GET请求到/weather/query端点
- response = requests.get(f"{base_url}/weather/query", params={"date": date, "city": city})
-
- # 检查响应是否成功
- if response.status_code == 200:
- print("Test Passed")
- print("Response JSON:", response.json())
- else:
- print("Test Failed")
- print("Response Status Code:", response.status_code)
- print("Response JSON:", response.json())
-
-if __name__ == "__main__":
- test_query_weather()
\ No newline at end of file
diff --git a/friday/api/weather/weather.py b/friday/api/weather/weather.py
deleted file mode 100644
index 4218fe0..0000000
--- a/friday/api/weather/weather.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# api/weather/weather.py
-from fastapi import APIRouter, HTTPException, Query
-import sqlite3
-
-router = APIRouter()
-
-@router.get("/weather/query") # 注意这里改为GET请求
-def query_weather(date: str, city: str): # 使用Query参数
- try:
- conn = sqlite3.connect('./database/weather.db')
- c = conn.cursor()
- c.execute("SELECT max_temp, min_temp, weather FROM weather WHERE city=? AND date=?", (city, date))
- row = c.fetchone()
- conn.close()
-
- if row:
- result=f'{date}, {city}: {row[2]}, {row[1]}-{row[0]} ℃'
- return {"result": str(result), "error": None}
- else:
- {"result": '', "error": 'data not found'}
-
- except Exception as e:
- print(e)
- return {"result": '', "error": 'not found'}
diff --git a/friday/api/wolfram_alpha/__init__.py b/friday/api/wolfram_alpha/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/core/__init__.py b/friday/core/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/core/action_manager.py b/friday/core/action_manager.py
deleted file mode 100644
index 731b1fd..0000000
--- a/friday/core/action_manager.py
+++ /dev/null
@@ -1,259 +0,0 @@
-__import__('pysqlite3')
-import sys
-sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
-
-from langchain.vectorstores import Chroma
-from langchain.embeddings.openai import OpenAIEmbeddings
-import argparse
-import json
-import os
-import re
-from dotenv import load_dotenv
-load_dotenv()
-OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
-OPENAI_ORGANIZATION = os.getenv('OPENAI_ORGANIZATION')
-
-
-class ActionManager:
- def __init__(self, config_path=None, action_lib_dir=None):
- # actions: Store the mapping relationship between descriptions and code (associated through task names)
- self.actions = {}
- self.action_lib_dir = action_lib_dir
-
- with open(f"{self.action_lib_dir}/actions.json") as f2:
- self.actions = json.load(f2)
- self.vectordb_path = f"{action_lib_dir}/vectordb"
-
- if not os.path.exists(self.vectordb_path):
- os.makedirs(self.vectordb_path)
- os.makedirs(f"{action_lib_dir}/code", exist_ok=True)
- os.makedirs(f"{action_lib_dir}/action_description", exist_ok=True)
- # Utilize the Chroma database and employ OpenAI Embeddings for vectorization (default: text-embedding-ada-002)
- self.vectordb = Chroma(
- collection_name="action_vectordb",
- embedding_function=OpenAIEmbeddings(
- openai_api_key=OPENAI_API_KEY,
- openai_organization=OPENAI_ORGANIZATION,
- ),
- persist_directory=self.vectordb_path,
- )
- assert self.vectordb._collection.count() == len(self.actions), (
- f"Action Manager's vectordb is not synced with actions.json.\n"
- f"There are {self.vectordb._collection.count()} actions in vectordb but {len(self.actions)} actions in actions.json.\n"
- )
-
- # View all the code in the code repository
- @property
- def programs(self):
- programs = ""
- for _, entry in self.actions.items():
- programs += f"{entry['code']}\n\n"
- return programs
-
- # Retrieve the descriptions of all actions
- @property
- def descriptions(self):
- descriptions = {}
- for action_name, entry in self.actions.items():
- descriptions.update({action_name: entry["description"]})
- return descriptions
-
- # Retrieve all action class names
- @property
- def action_names(self):
- return self.actions.keys()
-
- # View the code of a specific action
- def get_action_code(self, action_name):
- code = self.actions[action_name]['code']
- return code
-
- # Add new task code
- def add_new_action(self, info):
- program_name = info["task_name"]
- program_code = info["code"]
- program_description = info["description"]
- print(
- f"\033[33m {program_name}:\n{program_description}\033[0m"
- )
- # If this task code already exists in the action library, delete it and rewrite
- if program_name in self.actions:
- print(f"\033[33mAction {program_name} already exists. Rewriting!\033[0m")
- self.vectordb._collection.delete(ids=[program_name])
- # Store the new task code in the vector database and the action dictionary
- self.vectordb.add_texts(
- texts=[program_description],
- ids=[program_name],
- metadatas=[{"name": program_name}],
- )
- self.actions[program_name] = {
- "code": program_code,
- "description": program_description,
- }
- assert self.vectordb._collection.count() == len(
- self.actions
- ), "vectordb is not synced with actions.json"
- # Store the new task code and description in the action library, and enter the mapping relationship into the dictionary
- with open(f"{self.action_lib_dir}/code/{program_name}.py", "w") as fa:
- fa.write(program_code)
- with open(f"{self.action_lib_dir}/action_description/{program_name}.txt", "w") as fb:
- fb.write(program_description)
- with open(f"{self.action_lib_dir}/actions.json", "w") as fc:
- json.dump(self.actions,fc,indent=4)
- self.vectordb.persist()
-
- # Check if there are relevant tools
- def exist_action(self, action):
- if action in self.action_names:
- return True
- return False
-
- # Retrieve related task names
- def retrieve_action_name(self, query, k=10):
- k = min(self.vectordb._collection.count(), k)
- if k == 0:
- return []
- print(f"\033[33mAction Manager retrieving for {k} Actions\033[0m")
- # Retrieve descriptions of the top k related tasks.
- docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k)
- print(
- f"\033[33mAction Manager retrieved actions: "
- f"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}\033[0m"
- )
- action_name = []
- for doc, _ in docs_and_scores:
- action_name.append(doc.metadata["name"])
- return action_name
-
- # Return the task description based on the task name
- def retrieve_action_description(self, action_name):
- action_description = []
- for name in action_name:
- action_description.append(self.actions[name]["description"])
- return action_description
-
- # Return the task code based on the task name
- def retrieve_action_code(self, action_name):
- action_code = []
- for name in action_name:
- action_code.append(self.actions[name]["code"])
- return action_code
-
- # Delete task-related information
- def delete_action(self, action):
- # Delete the task from the vector database
- if action in self.actions:
- self.vectordb._collection.delete(ids=[action])
- print(
- f"\033[33m delete {action} from vectordb successfully! \033[0m"
- )
- # Delete the task from actions.json
- with open(f"{self.action_lib_dir}/actions.json", "r") as file:
- action_infos = json.load(file)
- if action in action_infos:
- del action_infos[action]
- with open(f"{self.action_lib_dir}/actions.json", "w") as file:
- json.dump(action_infos, file, indent=4)
- print(
- f"\033[33m delete {action} info from JSON successfully! \033[0m"
- )
- # del code
- code_path = f"{self.action_lib_dir}/code/{action}.py"
- if os.path.exists(code_path):
- os.remove(code_path)
- print(
- f"\033[33m delete {action} code successfully! \033[0m"
- )
- # del description
- description_path = f"{self.action_lib_dir}/action_description/{action}.txt"
- if os.path.exists(description_path):
- os.remove(description_path)
- print(
- f"\033[33m delete {action} description txt successfully! \033[0m"
- )
- # del args description
- args_path = f"{self.action_lib_dir}/args_description/{action}.txt"
- if os.path.exists(args_path):
- os.remove(args_path)
- print(
- f"\033[33m delete {action} args description txt successfully! \033[0m"
- )
-
-
-def print_error_and_exit(message):
- print(f"Error: {message}")
- sys.exit(1)
-
-
-def add_tool(actionManager, tool_name, tool_path):
- # Add your logic here to add the tool
- # code = ''
- with open(tool_path, 'r') as file:
- code = file.read()
-
- pattern = r'self\._description = "(.*?)"'
- match = re.search(pattern, code)
- if match:
- description = match.group(1)
- # print(description)
- # print(type(description))
- info = {
- "task_name" : tool_name,
- "code" : code,
- "description" : description
- }
- actionManager.add_new_action(info)
- print(f"Successfully add the tool: {tool_name} with path: {tool_path}")
- else:
- print_error_and_exit("No description found")
-
-
-def delete_tool(actionManager, tool_name):
- actionManager.delete_action(tool_name)
- print(f"Successfully Delete the tool: {tool_name}")
-
-
-def main():
- parser = argparse.ArgumentParser(description='Manage actions for FRIDAY')
-
- parser.add_argument('--add', action='store_true',
- help='Flag to add a new tool')
- parser.add_argument('--delete', action='store_true',
- help='Flag to delete a tool')
- parser.add_argument('--tool_name', type=str,
- help='Name of the tool to be added or deleted')
- parser.add_argument('--tool_path', type=str,
- help='Path of the tool to be added', required='--add' in sys.argv)
-
- args = parser.parse_args()
-
- actionManager = ActionManager(config_path=".env", action_lib_dir="friday/action_lib")
-
- if args.add:
- add_tool(actionManager, args.tool_name, args.tool_path)
- elif args.delete:
- delete_tool(actionManager, args.tool_name)
- else:
- print_error_and_exit("Please specify an operation type (add or del)")
-
-
-if __name__ == "__main__":
- main()
-
- # Retrieval
- # res = actionManager.retrieve_action_name("Open the specified text file in the specified folder using the default text viewer on Ubuntu.")
- # print(res[0])
-
- # Delete
- # actionManager.delete_action("zip_files")
-
- # Add
- # code = ''
- # with open("temp.py", 'r') as file:
- # code = file.read()
- # info = {
- # "task_name" : "XXX",
- # "code" : code,
- # "description" : "XXX"
- # }
- # actionManager.add_new_action(info)
diff --git a/friday/core/action_node.py b/friday/core/action_node.py
deleted file mode 100644
index ff98702..0000000
--- a/friday/core/action_node.py
+++ /dev/null
@@ -1,45 +0,0 @@
-class ActionNode:
- def __init__(self, name, description, type):
- self._name = name
- self._description = description
- self._return_val = ''
- self._relevant_code = {}
- self._next_action = {}
- self._status = False
- self._type = type
-
- @property
- def name(self):
- return self._name
-
- @property
- def description(self):
- return self._description
-
- @property
- def return_val(self):
- return self._return_val
-
- @property
- def relevant_action(self):
- return self._relevant_code
-
- @property
- def status(self):
- return self._status
-
- @property
- def type(self):
- return self._type
-
- @property
- def next_action(self):
- return self._next_action
-
- def __str__(self):
- return f"name: {self.name} \n description: {self.description} \n return: {self.return_val} \n relevant_action: {self._relevant_code} \n next_action: {self.next_action} \n status: {self.status} \n type: {self.type}"
-
-
-if __name__ == '__main__':
- node = ActionNode('temp','xxx')
- print(node.name)
\ No newline at end of file
diff --git a/friday/core/api_server.py b/friday/core/api_server.py
deleted file mode 100644
index 6872d85..0000000
--- a/friday/core/api_server.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import os
-
-from fastapi import FastAPI
-from friday.core.server_config import ConfigManager
-
-app = FastAPI()
-
-# Import your services
-from friday.api.python.interpreter import router as python_router
-from friday.api.arxiv.arxiv import router as arxiv_router
-from friday.api.bing.bing_service import router as bing_router
-from friday.api.calculator.calculator import router as calculator_router
-from friday.api.chemical.chemical import router as chemical_router
-from friday.api.ppt.ppt import router as ppt_router
-from friday.api.shell.shell import router as shell_router
-from friday.api.database.database import router as db_router
-from friday.api.wolfram_alpha.wolfram_alpha import router as wa_router
-from friday.api.weather.weather import router as weather_router
-from friday.api.google_calendar.calendar_service import router as calendar_router
-from friday.api.gmail.gmail import router as gmail_router
-from friday.api.markdown.markdown_service import router as markdown_router
-
-from starlette.middleware.base import BaseHTTPMiddleware
-from starlette.requests import Request
-
-
-class LoggingMiddleware(BaseHTTPMiddleware):
- async def dispatch(self, request: Request, call_next):
- print(f"Incoming request: {request.method} {request.url}")
- try:
- response = await call_next(request)
- except Exception as e:
- print(f"Request error: {str(e)}")
- raise e from None
- else:
- print(f"Outgoing response: {response.status_code}")
- return response
-
-
-app.add_middleware(LoggingMiddleware)
-
-# Create a dictionary that maps service names to their routers
-services = {
- "python_executor": python_router,
- "calculator": calculator_router,
- "arxiv": arxiv_router,
- "bing": bing_router,
- "chemical": chemical_router,
- "ppt": ppt_router,
- "shell": shell_router,
- "database": db_router,
- "wolframalpha": wa_router,
- "weather": weather_router,
- "calendar": calendar_router,
- "gmail": gmail_router,
- "markdown": markdown_router
-
-}
-
-server_list = ["python_executor", "calculator","arxiv","bing","shell","ppt",
- "database","wolframalpha","weather","calendar","gmail","markdown"]
-
-# Include only the routers for the services listed in server_list
-for service in server_list:
- if service in services:
- app.include_router(services[service])
-
-# proxy_manager = ConfigManager()
-# proxy_manager.apply_proxies()
-
-if __name__ == "__main__":
- import uvicorn
-
- uvicorn.run(app, host="0.0.0.0", port=8079)
diff --git a/friday/core/llms.py b/friday/core/llms.py
deleted file mode 100644
index 0d0b4c3..0000000
--- a/friday/core/llms.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import openai
-import json
-import logging
-import os
-from dotenv import load_dotenv
-
-
-load_dotenv()
-MODEL_NAME = os.getenv('MODEL_NAME')
-OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
-OPENAI_ORGANIZATION = os.getenv('OPENAI_ORGANIZATION')
-
-
-class OpenAI:
- """
- OPEN AI Chat Models
- """
- def __init__(self, config_path=None):
-
- self.model_name = MODEL_NAME
- openai.api_key = OPENAI_API_KEY
- openai.organization = OPENAI_ORGANIZATION
- # print(openai.api_key)
- # print(openai.organization)
- # openai.proxy = proxy
-
- def chat(self, messages, temperature=0, sleep_time=2):
- response = openai.chat.completions.create(
- model=self.model_name,
- messages=messages,
- temperature=temperature
- )
- logging.info(f"Response: {response.choices[0].message.content}")
-
- # time.sleep(sleep_time)
- # return response['choices'][0]['message']
- return response.choices[0].message.content
-
-
diff --git a/friday/core/openapi.json b/friday/core/openapi.json
deleted file mode 100644
index b2a7542..0000000
--- a/friday/core/openapi.json
+++ /dev/null
@@ -1,723 +0,0 @@
-{
- "openapi": "3.1.0",
- "info": {
- "title": "FastAPI",
- "version": "0.1.0"
- },
- "paths": {
- "/tools/markdown/web2md": {
- "get": {
- "summary": "This API can only get the markdown formatting of a web page at a given url but can not do summary work.",
- "operationId": "get_web_md_tools_markdown_web2md_get",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/TargetPageModel"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/tools/bing/searchv2": {
- "get": {
- "summary": "Execute Bing Search - returns top web snippets related to the query. Avoid using complex filters like 'site:'. For detailed page content, further use the web browser tool.",
- "operationId": "bing_search_v2_tools_bing_searchv2_get",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/QueryItemV2"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/tools/bing/load_pagev2": {
- "get": {
- "summary": "Web browser tool for detailed content retrieval and specific information extraction from a target URL.In the case of Wikipedia, the number of tokens on such pages is often too large to load the entire page, so the 'query' parameter must be given to perform a similarity query to find the most relevant pieces of content. The 'query' parameter should be assigned with your task description to find the most relevant content of the web page.It is important that your 'query' must retain enough details about the task, such as time, location, quantity, and other information, to ensure that the results obtained are accurate enough.",
- "operationId": "load_page_v2_tools_bing_load_pagev2_get",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PageItemV2"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/weather/query": {
- "get": {
- "summary": "Query Weather",
- "operationId": "query_weather_weather_query_get",
- "parameters": [
- {
- "name": "date",
- "in": "query",
- "required": true,
- "schema": {
- "type": "string",
- "title": "Date"
- }
- },
- {
- "name": "city",
- "in": "query",
- "required": true,
- "schema": {
- "type": "string",
- "title": "City"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/gmail/send": {
- "post": {
- "summary": "Send google Email",
- "operationId": "send_test_email_gmail_send_post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/EmailSchema"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/gmail/list": {
- "get": {
- "summary": "List Recent Emails from gmail",
- "operationId": "list_recent_emails_gmail_list_get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- }
- }
- }
- },
- "/tools/audio2text": {
- "post": {
- "summary": "A tool that converts audio to natural language text",
- "operationId": "audio2text_tools_audio2text_post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_audio2text_tools_audio2text_post"
- }
- }
- },
- "required": true
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- },
- "/tools/image_caption": {
- "post": {
- "summary": "When the task is to question and answer based on local picture, you have to use the Image Caption tool, who can directly analyze picture to answer question and complete task. For local images you want to understand, you need to only give the image_file without url. It is crucial to provide the 'query' parameter, and its value must be the full content of the task itself.",
- "operationId": "image_search_tools_image_caption_post",
- "parameters": [
- {
- "name": "query",
- "in": "query",
- "required": false,
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "default": "What's in this image?",
- "title": "Query"
- }
- },
- {
- "name": "url",
- "in": "query",
- "required": false,
- "schema": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Url"
- }
- }
- ],
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_image_search_tools_image_caption_post"
- }
- ],
- "title": "Body"
- }
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {}
- }
- }
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- }
- }
- }
- }
- }
- },
- "components": {
- "schemas": {
- "AddFirstPageModel": {
- "properties": {
- "title": {
- "type": "string",
- "title": "Title"
- },
- "subtitle": {
- "type": "string",
- "title": "Subtitle"
- }
- },
- "type": "object",
- "required": [
- "title",
- "subtitle"
- ],
- "title": "AddFirstPageModel"
- },
- "AddTextImagePageModel": {
- "properties": {
- "title": {
- "type": "string",
- "title": "Title"
- },
- "bullet_items": {
- "type": "string",
- "title": "Bullet Items"
- },
- "image": {
- "type": "string",
- "title": "Image"
- }
- },
- "type": "object",
- "required": [
- "title",
- "bullet_items",
- "image"
- ],
- "title": "AddTextImagePageModel"
- },
- "AddTextPageModel": {
- "properties": {
- "title": {
- "type": "string",
- "title": "Title"
- },
- "bullet_items": {
- "type": "string",
- "title": "Bullet Items"
- }
- },
- "type": "object",
- "required": [
- "title",
- "bullet_items"
- ],
- "title": "AddTextPageModel"
- },
- "CalendarEvent": {
- "properties": {
- "summary": {
- "type": "string",
- "title": "Summary"
- },
- "location": {
- "type": "string",
- "title": "Location"
- },
- "description": {
- "type": "string",
- "title": "Description"
- },
- "start": {
- "type": "object",
- "title": "Start",
- "example": {
- "dateTime": "2023-07-31T15:00:00",
- "timeZone": "Asia/Shanghai"
- }
- },
- "end": {
- "type": "object",
- "title": "End",
- "example": {
- "dateTime": "2023-07-31T16:00:00",
- "timeZone": "Asia/Shanghai"
- }
- }
- },
- "type": "object",
- "required": [
- "summary",
- "location",
- "description",
- "start",
- "end"
- ],
- "title": "CalendarEvent"
- },
- "CreateFileModel": {
- "properties": {
- "theme": {
- "type": "string",
- "title": "Theme"
- }
- },
- "type": "object",
- "required": [
- "theme"
- ],
- "title": "CreateFileModel"
- },
- "EmailSchema": {
- "properties": {
- "from_email": {
- "type": "string",
- "title": "From Email"
- },
- "to_email": {
- "type": "string",
- "title": "To Email"
- },
- "subject": {
- "type": "string",
- "title": "Subject"
- },
- "content": {
- "type": "string",
- "title": "Content"
- }
- },
- "type": "object",
- "required": [
- "from_email",
- "to_email",
- "subject",
- "content"
- ],
- "title": "EmailSchema"
- },
- "Expression": {
- "properties": {
- "expression": {
- "type": "string",
- "title": "Expression"
- }
- },
- "type": "object",
- "required": [
- "expression"
- ],
- "title": "Expression"
- },
- "GetImageModel": {
- "properties": {
- "keywords": {
- "type": "string",
- "title": "Keywords"
- }
- },
- "type": "object",
- "required": [
- "keywords"
- ],
- "title": "GetImageModel"
- },
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {
- "$ref": "#/components/schemas/ValidationError"
- },
- "type": "array",
- "title": "Detail"
- }
- },
- "type": "object",
- "title": "HTTPValidationError"
- },
- "Item": {
- "properties": {
- "code": {
- "type": "string",
- "title": "Code"
- }
- },
- "type": "object",
- "required": [
- "code"
- ],
- "title": "Item"
- },
- "PageItemV2": {
- "properties": {
- "url": {
- "type": "string",
- "title": "Url"
- },
- "query": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "title": "Query"
- }
- },
- "type": "object",
- "required": [
- "url"
- ],
- "title": "PageItemV2"
- },
- "QueryItem": {
- "properties": {
- "query": {
- "type": "string",
- "title": "Query"
- }
- },
- "type": "object",
- "required": [
- "query"
- ],
- "title": "QueryItem"
- },
- "Body_image_search_tools_image_caption_post": {
- "properties": {
- "image_file": {
- "anyOf": [
- {
- "type": "string",
- "format": "binary"
- },
- {
- "type": "null"
- }
- ],
- "title": "Image File"
- }
- },
- "type": "object",
- "title": "Body_image_search_tools_image_caption_post"
- },
- "Body_audio2text_tools_audio2text_post": {
- "properties": {
- "file": {
- "type": "string",
- "format": "binary",
- "title": "File"
- }
- },
- "type": "object",
- "required": [
- "file"
- ],
- "title": "Body_audio2text_tools_audio2text_post"
- },
- "QueryItemV2": {
- "properties": {
- "query": {
- "type": "string",
- "title": "Query"
- },
- "top_k": {
- "anyOf": [
- {
- "type": "integer"
- },
- {
- "type": "null"
- }
- ],
- "title": "Top K"
- }
- },
- "type": "object",
- "required": [
- "query"
- ],
- "title": "QueryItemV2"
- },
- "TargetPageModel": {
- "properties": {
- "url": {
- "type": "string",
- "title": "Url"
- }
- },
- "type": "object",
- "required": [
- "url"
- ],
- "title": "TargetPageModel"
- },
- "SQLRequest": {
- "properties": {
- "queries": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "Queries"
- }
- },
- "type": "object",
- "required": [
- "queries"
- ],
- "title": "SQLRequest"
- },
- "ShellCommandModel": {
- "properties": {
- "command": {
- "type": "string",
- "title": "Command"
- }
- },
- "type": "object",
- "required": [
- "command"
- ],
- "title": "ShellCommandModel"
- },
- "ShellCommandResultModel": {
- "properties": {
- "stdout": {
- "type": "string",
- "title": "Stdout"
- },
- "stderr": {
- "type": "string",
- "title": "Stderr"
- }
- },
- "type": "object",
- "required": [
- "stdout",
- "stderr"
- ],
- "title": "ShellCommandResultModel"
- },
- "ValidationError": {
- "properties": {
- "loc": {
- "items": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "integer"
- }
- ]
- },
- "type": "array",
- "title": "Location"
- },
- "msg": {
- "type": "string",
- "title": "Message"
- },
- "type": {
- "type": "string",
- "title": "Error Type"
- }
- },
- "type": "object",
- "required": [
- "loc",
- "msg",
- "type"
- ],
- "title": "ValidationError"
- },
- "ArxivQuery": {
- "properties": {
- "query": {
- "type": "string",
- "title": "Query"
- }
- },
- "type": "object",
- "required": [
- "query"
- ],
- "title": "ArxivQuery"
- }
-
- }
- }
-}
\ No newline at end of file
diff --git a/friday/core/schema.py b/friday/core/schema.py
deleted file mode 100644
index 44917cd..0000000
--- a/friday/core/schema.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from dataclasses import dataclass, field
-from enum import Enum
-from typing import Dict, List, Optional, Union
-
-
-class ActionStatusCode(int, Enum):
- ING = 0
- SUCCESS = 1
- FAILED = -1
-
-
-class ActionValidCode(int, Enum):
- FINISH = 1
- OPEN = 0
- CLOSED = -1
- INVALID = -2
- ABSENT = -3 # NO ACTION
-
-
-@dataclass
-class ActionReturn:
- args: Dict
- url: Optional[str] = None
- type: Optional[str] = None
- result: Optional[str] = None
- errmsg: Optional[str] = None
- state: Union[ActionStatusCode, int] = ActionStatusCode.SUCCESS
- thought: Optional[str] = None
- valid: Optional[ActionValidCode] = ActionValidCode.OPEN
-
-@dataclass
-class EnvState:
- command: List[str] = field(default_factory=list)
- result: Optional[str] = None
- error: Optional[str] = None
- pwd: Optional[str] = None
- ls: Optional[str] = None
-
- def __str__(self):
- return (f"Result: {self.result}\n"
- f"Error: {self.error}\n"
- f"PWD: {self.pwd}\n"
- f"LS: {self.ls}")
\ No newline at end of file
diff --git a/friday/core/server_config.py b/friday/core/server_config.py
deleted file mode 100644
index 10479ca..0000000
--- a/friday/core/server_config.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-
-class ConfigManager:
- _instance = None
-
- def __new__(cls):
- if cls._instance is None:
- cls._instance = super(ConfigManager, cls).__new__(cls)
- cls._instance.http_proxy = "http://127.0.0.1:10809"
- cls._instance.https_proxy = "http://127.0.0.1:10809"
- # cls._instance.http_proxy = None
- # cls._instance.https_proxy = None
- return cls._instance
-
- def set_proxies(self, http, https):
- self.http_proxy = http
- self.https_proxy = https
-
- def apply_proxies(self):
- if self.http_proxy:
- os.environ["http_proxy"] = self.http_proxy
- if self.https_proxy:
- os.environ["https_proxy"] = self.https_proxy
-
- def clear_proxies(self):
- os.environ.pop("http_proxy", None)
- os.environ.pop("https_proxy", None)
-
diff --git a/friday/core/tool_planner.py b/friday/core/tool_planner.py
deleted file mode 100644
index 3ddeef3..0000000
--- a/friday/core/tool_planner.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import requests
-import json
-import os
-from friday.action.get_os_version import get_os_version, check_os_version
-from friday.core.llms import OpenAI
-
-sys_prompt = '''
-You are a helpful assistant that can answer the user's questions with the help of tools.You are provided with the following tools:
-Tools:
-1. {{
- "tool_name": "bing_search",
- "description": "You can use this tool to search top 10 relevant web pages' link and very short description for the given query keywords.You sometimes need to use web browser to get detailed information of some page"
- "path": "/api/tools/bing/search",
- "method": "get",
- "params": [
- {{
- "name": "query",
- "in":"query",
- "type": "string",
- "description": "the query keywords for bing search"
- }},
-
- ],
-
-}}
-2. {{
- "tool_name": "web_browser",
- "description": "You can use this tool to browser the detail content of the web page given its url"
- "path": "/api/tools/bing/search",
- "method": "get",
- "params": [
- {{
- "name": "query",
- "in":"query",
- "type": "string",
- "description": "the query keywords for bing search"
- }},
-
- ],
-
-}}
-You need to decide whether which tools to use to solve the question asked by user.
-Remember if you can make sure the answer you give by your own knowledge is completely accurate, don't use any tools. Only use the tools provided to you if you lack some external knowledge to give a factual answer. You can even use multiple tools or use the same tool multiple times if necessary.
-You should give me a plan list to tell me how to use the tools to solve the question,the response format should just like:
-eg. To solve the question "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)?"
-1. /api/tools/bing/search?query=Mercedes Sosa
-2. /api/tools/bing/load_page?url=https://en.wikipedia.org/wiki/Mercedes_Sosa
-If you think there is no need to use tools, you can just respond:
-There is no need to use tools.
-Now,you can start to solve the question,give me your plans:
-{question}
-'''
-
-os.environ["BING_SUBSCRIPTION_KEY"] = "885e62a126554fb390af88ae31d2c8ff"
-os.environ["BING_SEARCH_URL"] = "https://api.bing.microsoft.com/v7.0/search"
-# search = BingSearchAPIWrapper()
-
-# res = search.results("https://zhuanlan.zhihu.com/p/623421034 summary",10)
-class ToolPlanner():
- """
- SkillCreator is used to generate new skills and store them in the action_lib.
- """
- def __init__(self, config_path=None) -> None:
- super().__init__()
- self.llm = OpenAI(config_path)
- self.system_version = get_os_version()
- try:
- check_os_version(self.system_version)
- except ValueError as e:
- print(e)
- # self.mac_systom_prompts =
-
- def format_message(self, task):
- self.prompt = sys_prompt.format(question=task)
- # self.prompt = ""
- print(self.prompt)
- self.message = [
- {"role": "system", "content": self.prompt},
- {"role": "user", "content": task},
- ]
- return self.llm.chat(self.message)
-q = '''
-What writer is quoted by Merriam-Webster for the Word of the Day from June 27, 2022?
-'''
-res = ToolPlanner("../../examples/config.json").format_message(q)
-print(res)
-
-
\ No newline at end of file
diff --git a/friday/core/tool_request_util.py b/friday/core/tool_request_util.py
deleted file mode 100644
index ec573de..0000000
--- a/friday/core/tool_request_util.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import requests
-class ToolRequestUtil:
- def __init__(self):
- self.session = requests.session()
- self.headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML like Gecko) Chrome/52.0.2743.116 Safari/537.36'}
- self.base_url = "http://43.159.144.130:8079"
-
- def request(self, api_path, method, params=None, files=None, content_type="application/json"):
- """
- :param api_path: the path of the api
- :param method: get/post
- :param params: the params of the api, can be None
- :param files: files to be uploaded, can be None
- :param content_type: the content_type of api, e.g., application/json, multipart/form-data, can be None
- :return: the return of the api
- """
- url = self.base_url + api_path
- try:
- # 判断请求方法
- if method.lower() == "get":
- if content_type == "application/json":
- result = self.session.get(url=url, json=params, headers=self.headers, timeout=60).json()
- else:
- result = self.session.get(url=url, params=params, headers=self.headers, timeout=60).json()
- elif method.lower() == "post":
- if content_type == "multipart/form-data":
- result = self.session.post(url=url, files=files, data=params, headers=self.headers).json()
- elif content_type == "application/json":
- result = self.session.post(url=url, json=params, headers=self.headers).json()
- else:
- result = self.session.post(url=url, data=params, headers=self.headers).json()
- else:
- print("request method error!")
- return None
- return result
- except Exception as e:
- print("http request error: %s" % e)
- return None
\ No newline at end of file
diff --git a/friday/core/utils.py b/friday/core/utils.py
deleted file mode 100644
index b7d7dfe..0000000
--- a/friday/core/utils.py
+++ /dev/null
@@ -1,305 +0,0 @@
-import copy
-import numpy as np
-import itertools
-import json
-import logging
-import os
-import re
-import string
-from typing import Any
-import tqdm
-import re
-import tiktoken
-
-def num_tokens_from_string(string: str) -> int:
- """Returns the number of tokens in a text string."""
- encoding = tiktoken.encoding_for_model('gpt-4-1106-preview')
- num_tokens = len(encoding.encode(string))
- return num_tokens
-
-
-
-def parse_content(content, type="html.parser"):
- implemented = ["html.parser", "lxml", "lxml-xml", "xml", "html5lib"]
- if type not in implemented:
- raise ValueError(f"Parser type {type} not implemented. Please choose one of {implemented}")
-
- from bs4 import BeautifulSoup
-
- soup = BeautifulSoup(content, type)
- original_size = len(str(soup.get_text()))
-
- tags_to_exclude = [
- "nav",
- "aside",
- "form",
- "header",
- "noscript",
- "svg",
- "canvas",
- "footer",
- "script",
- "style",
- ]
- for tag in soup(tags_to_exclude):
- tag.decompose()
-
- ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"]
- for id in ids_to_exclude:
- tags = soup.find_all(id=id)
- for tag in tags:
- tag.decompose()
-
- classes_to_exclude = [
- "elementor-location-header",
- "navbar-header",
- "nav",
- "header-sidebar-wrapper",
- "blog-sidebar-wrapper",
- "related-posts",
- ]
- for class_name in classes_to_exclude:
- tags = soup.find_all(class_=class_name)
- for tag in tags:
- tag.decompose()
-
- content = soup.get_text()
- content = clean_string(content)
-
- cleaned_size = len(content)
- if original_size != 0:
- logging.info(
- f"Cleaned page size: {cleaned_size} characters, down from {original_size} (shrunk: {original_size-cleaned_size} chars, {round((1-(cleaned_size/original_size)) * 100, 2)}%)" # noqa:E501
- )
-
- return content
-
-
-def clean_string(text):
- """
- This function takes in a string and performs a series of text cleaning operations.
-
- Args:
- text (str): The text to be cleaned. This is expected to be a string.
-
- Returns:
- cleaned_text (str): The cleaned text after all the cleaning operations
- have been performed.
- """
- # Replacement of newline characters:
- text = text.replace("\n", " ")
-
- # Stripping and reducing multiple spaces to single:
- cleaned_text = re.sub(r"\s+", " ", text.strip())
-
- # Removing backslashes:
- cleaned_text = cleaned_text.replace("\\", "")
-
- # Replacing hash characters:
- cleaned_text = cleaned_text.replace("#", " ")
-
- # Eliminating consecutive non-alphanumeric characters:
- # This regex identifies consecutive non-alphanumeric characters (i.e., not
- # a word character [a-zA-Z0-9_] and not a whitespace) in the string
- # and replaces each group of such characters with a single occurrence of
- # that character.
- # For example, "!!! hello !!!" would become "! hello !".
- cleaned_text = re.sub(r"([^\w\s])\1*", r"\1", cleaned_text)
-
- return cleaned_text
-
-
-def is_readable(s):
- """
- Heuristic to determine if a string is "readable" (mostly contains printable characters and forms meaningful words)
-
- :param s: string
- :return: True if the string is more than 95% printable.
- """
- try:
- printable_ratio = sum(c in string.printable for c in s) / len(s)
- except ZeroDivisionError:
- logging.warning("Empty string processed as unreadable")
- printable_ratio = 0
- return printable_ratio > 0.95 # 95% of characters are printable
-
-
-
-
-
-def format_source(source: str, limit: int = 20) -> str:
- """
- Format a string to only take the first x and last x letters.
- This makes it easier to display a URL, keeping familiarity while ensuring a consistent length.
- If the string is too short, it is not sliced.
- """
- if len(source) > 2 * limit:
- return source[:limit] + "..." + source[-limit:]
- return source
-
-
-
-
-# check if the source is valid json string
-def is_valid_json_string(source: str):
- try:
- _ = json.loads(source)
- return True
- except json.JSONDecodeError:
- logging.error(
- "Insert valid string format of JSON. \
- Check the docs to see the supported formats - `https://docs.embedchain.ai/data-sources/json`"
- )
- return False
-
-
-
-
-def chunks(iterable, batch_size=100, desc="Processing chunks"):
- """A helper function to break an iterable into chunks of size batch_size."""
- it = iter(iterable)
- total_size = len(iterable)
-
- with tqdm(total=total_size, desc=desc, unit="batch") as pbar:
- chunk = tuple(itertools.islice(it, batch_size))
- while chunk:
- yield chunk
- pbar.update(len(chunk))
- chunk = tuple(itertools.islice(it, batch_size))
-
-def generate_prompt(template: str, replace_dict: dict):
- prompt = copy.deepcopy(template)
- for k, v in replace_dict.items():
- prompt = prompt.replace(k, str(v))
- return prompt
-
-def cosine_similarity(a, b):
- return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))\
-
-
-def get_open_api_description_pair():
- script_dir = os.path.dirname(os.path.abspath(__file__))
- open_api_path = os.path.join(script_dir, 'openapi.json')
- with open(open_api_path, 'r') as file:
- open_api_json = json.load(file)
- open_api_dict = open_api_json['paths']
- open_api_description_pair = {}
- for name, value in open_api_dict.items():
- if 'post' in value:
- open_api_description_pair[name] = value['post']['summary']
- else:
- open_api_description_pair[name] = value['get']['summary']
- return open_api_description_pair
-
-def get_open_api_doc_path():
- script_dir = os.path.dirname(os.path.abspath(__file__))
- open_api_path = os.path.join(script_dir, 'openapi.json')
- return open_api_path
-
-
-# prompt = '''
-# 'Sheet1': Zone 1 Unnamed: 1 Unnamed: 2 Unnamed: 3 \
-# 0 Name Type Revenue Rent
-# 1 Rainforest Bistro Restaurant 32771 1920
-# 2 Panorama Outfitters Apparel 23170 1788
-# 3 Zack's Cameras and Trail Mix Electronics / Food 33117 1001
-# 4 SignPro Custom DeSign Signage 21246 1121
-# 5 Zone 2 NaN NaN NaN
-# 6 Serenity Indoor Fountains Decor 25234 6359
-# 7 Budapest Comics Comics 12251 2461
-# 8 Dottie's Lattes Restaurant 34427 1293
-# 9 Zone 3 NaN NaN NaN
-# 10 Gumball Utopia Candy 13271 3420
-# 11 Your Uncle's Basement Sports Collectibles 11119 8201
-# 12 Carnivore Loan Specialists Finance 31000 50312
-# 13 Harry's Steakhouse Restaurant 46791 1327
-# 14 Two Guys Paper Supplies Office Supplies 76201 1120
-# 15 Dragon Pizza Restaurant 10201 2000
-# 16 Zone 4 NaN NaN NaN
-# 17 Us Three: The U2 Fan Store Music 10201 1200
-# 18 Jimmy's Buffett Restaurant 10027 3201
-# 19 Franz Equipment Rentals Industrial Supplies 20201 2201
-# 20 Nigel's Board Games Board Games 62012 2013
-# 21 Destructor's Den Baby Supplies 79915 5203
-# 22 Hook Me Up Sporting Goods 56503 1940
-# 23 Zone 5 (Food Court) NaN NaN NaN
-# 24 Slam Dunk Restaurant 61239 5820
-# 25 Ben's Hungarian-Asian Fusion Restaurant 68303 2011
-# 26 PleaseBurgers Restaurant 20132 1402
-# 27 Reagan's Vegan Restaurant 20201 6201
-# 28 FreshCart Store-to-Table Restaurant 83533 2751
-
-# Unnamed: 4
-# 0 Opened
-# 1 2023-07-19 00:00:00
-# 2 2023-06-11 00:00:00
-# 3 2023-05-12 00:00:00
-# 4 2023-01-30 00:00:00
-# 5 NaN
-# 6 2023-05-01 00:00:00
-# 7 2023-01-03 00:00:00
-# 8 2023-05-31 00:00:00
-# 9 NaN
-# 10 2023-11-04 00:00:00
-# 11 2023-01-10 00:00:00
-# 12 2023-03-09 00:00:00
-# 13 2023-01-08 00:00:00
-# 14 2023-09-20 00:00:00
-# 15 2023-01-20 00:00:00
-# 16 NaN
-# 17 2023-09-20 00:00:00
-# 18 2023-01-20 00:00:00
-# 19 2023-03-06 00:00:00
-# 20 2023-01-07 00:00:00
-# 21 2023-02-06 00:00:00
-# 22 2023-05-07 00:00:00
-# 23 NaN
-# 24 2023-10-20 00:00:00
-# 25 2023-02-12 00:00:00
-# 26 2023-02-15 00:00:00
-# 27 2023-07-20 00:00:00
-# 28 2023-12-08 00:00:00
-# '''
-# print(num_tokens_from_string(prompt))
-
-
-# {
-# "paths": {
-# "/tools/python": {
-# "post": {
-# "summary": "Execute Python",
-# "operationId": "execute_python_tools_python_post",
-# "requestBody": {
-# "content": {
-# "application/json": {
-# "schema": {
-# "$ref": "#/components/schemas/Item"
-# }
-# }
-# },
-# "required": true
-# },
-# "responses": {
-# "200": {
-# "description": "Successful Response",
-# "content": {
-# "application/json": {
-# "schema": {}
-# }
-# }
-# },
-# "422": {
-# "description": "Validation Error",
-# "content": {
-# "application/json": {
-# "schema": {
-# "$ref": "#/components/schemas/HTTPValidationError"
-# }
-# }
-# }
-# }
-# }
-# }
-# }
-# }
-# }
\ No newline at end of file
diff --git a/friday/environment/__init__.py b/friday/environment/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/friday/environment/env.py b/friday/environment/env.py
deleted file mode 100644
index a2804d1..0000000
--- a/friday/environment/env.py
+++ /dev/null
@@ -1,79 +0,0 @@
-import os
-
-from typing import Optional, Union, List
-from friday.core.schema import EnvState
-
-
-class Env:
- """Base class for all actions.
-
- Args:
- description (str, optional): The description of the action. Defaults to
- None.
- name (str, optional): The name of the action. If None, the name will
- be class name. Defaults to None.
- """
-
- def __init__(self) -> None:
- self._name: str = self.__class__.__name__
- self.timeout: int = 300
- self.working_dir = os.path.abspath(os.path.join(__file__, "..", "..", "..", "working_dir"))
- if not os.path.exists(self.working_dir):
- os.makedirs(self.working_dir)
-
- self.env_state: Union[EnvState, None] = None
-
- def list_working_dir(self):
- """
- Lists files and directories in the given directory with details similar to 'ls' command in Linux.
- """
- directory = self.working_dir
- # Check if the directory exists
- if not os.path.exists(directory):
- return f"Directory '{directory}' does not exist."
-
- # List files and directories
- files_and_dirs = os.listdir(directory)
-
- # Create a list to store the details
- details = []
-
- for name in files_and_dirs:
- # Get the full path
- full_path = os.path.join(directory, name)
-
- # Get file or directory size
- size = os.path.getsize(full_path)
-
- # Check if it's a file or directory
- if os.path.isdir(full_path):
- type = 'Directory'
- else:
- type = 'File'
-
- details.append(f"{name}\t {size} bytes\t {type}")
-
- return "\n".join(details)
-
-
-
- def step(self, _command) -> EnvState:
- raise NotImplementedError
-
- def reset(self):
- raise NotImplementedError
- @property
- def name(self):
- return self._name
-
- def __repr__(self):
- return f'{self.name}'
-
- def __str__(self):
- return self.__repr__()
-
-
-if __name__ == '__main__':
- env = Env()
- env.env_state = EnvState()
- # result = env.observe()
diff --git a/oscopilot/__init__.py b/oscopilot/__init__.py
new file mode 100644
index 0000000..73a8639
--- /dev/null
+++ b/oscopilot/__init__.py
@@ -0,0 +1,6 @@
+from .agents import *
+from .prompts import *
+from .utils import *
+from .environments import *
+from .modules import *
+from .tool_repository import *
\ No newline at end of file
diff --git a/friday/action/__init__.py b/oscopilot/environments/__init__.py
similarity index 100%
rename from friday/action/__init__.py
rename to oscopilot/environments/__init__.py
diff --git a/friday/environment/bash_env.py b/oscopilot/environments/bash_env.py
similarity index 95%
rename from friday/environment/bash_env.py
rename to oscopilot/environments/bash_env.py
index bf8ace4..c38171e 100644
--- a/friday/environment/bash_env.py
+++ b/oscopilot/environments/bash_env.py
@@ -1,8 +1,8 @@
import subprocess
import os
-from friday.core.schema import EnvState
-from friday.environment.env import Env
+from oscopilot.utils.schema import EnvState
+from oscopilot.environments.env import Env
class BashEnv(Env):
diff --git a/oscopilot/environments/env.py b/oscopilot/environments/env.py
new file mode 100644
index 0000000..c9b2be1
--- /dev/null
+++ b/oscopilot/environments/env.py
@@ -0,0 +1,136 @@
+import os
+from oscopilot.utils.config import Config
+from typing import Optional, Union, List
+from oscopilot.utils.schema import EnvState
+
+
+class Env:
+ """
+ A base class for environments configurations in action-based systems.
+
+ This class provides foundational attributes and methods for managing environments,
+ including timeouts, working directories, and environmental states. It is designed
+ to be extended by subclasses that implement specific environments behaviors.
+ """
+
+ def __init__(self) -> None:
+ """
+ Initializes the environments with default settings.
+
+ Sets up the working directory, applying a default timeout and preparing the
+ environments state. If the working directory does not exist, it is created.
+ """
+ self._name: str = self.__class__.__name__
+ self.timeout: int = 300
+ working_dir = Config.get_parameter('working_dir')
+ if os.path.isabs(working_dir):
+ self.working_dir = working_dir
+ else:
+ self.working_dir = os.path.abspath(os.path.join(__file__, "..", "..", "..", working_dir))
+ if not os.path.exists(self.working_dir):
+ os.makedirs(self.working_dir)
+
+ self.env_state: Union[EnvState, None] = None
+
+ def list_working_dir(self):
+ """
+ Lists the contents of the working directory in a detailed format.
+
+ Returns a string representation similar to the output of the 'ls' command in Linux,
+ including file/directory names, sizes, and types.
+
+ Returns:
+ str: Detailed listings of the working directory's contents, or an error message if the directory does not exist.
+ """
+ directory = self.working_dir
+ # Check if the directory exists
+ if not os.path.exists(directory):
+ return f"Directory '{directory}' does not exist."
+
+ # List files and directories
+ files_and_dirs = os.listdir(directory)
+
+ # Create a list to store the details
+ details = []
+
+ for name in files_and_dirs:
+ # Get the full path
+ full_path = os.path.join(directory, name)
+
+ # Get file or directory size
+ size = os.path.getsize(full_path)
+
+ # Check if it's a file or directory
+ if os.path.isdir(full_path):
+ doc_type = 'Directory'
+ else:
+ doc_type = 'File'
+
+ details.append(f"{name}\t {size} bytes\t {doc_type}")
+
+ return "\n".join(details)
+
+ def step(self, _command) -> EnvState:
+ """
+ Executes a command within the environments.
+
+ This method is intended to be implemented by subclasses, defining how commands
+ are processed and their effects on the environments state.
+
+ Args:
+ _command: The command to be executed.
+
+ Raises:
+ NotImplementedError: Indicates that the subclass must implement this method.
+
+ Returns:
+ EnvState: The state of the environments after executing the command.
+ """
+ raise NotImplementedError
+
+ def reset(self):
+ """
+ Resets the environments to its initial state.
+
+ This method is intended to be implemented by subclasses, defining the specific
+ actions required to reset the environments.
+ """
+ working_dir = Config.get_parameter('working_dir')
+ if os.path.isabs(working_dir):
+ self.working_dir = working_dir
+ else:
+ self.working_dir = os.path.abspath(os.path.join(__file__, "..", "..", "..", working_dir))
+
+ @property
+ def name(self):
+ """
+ The name of the environments.
+
+ Returns:
+ str: The name of the environments, typically set to the class name unless overridden in a subclass.
+ """
+ return self._name
+
+ def __repr__(self):
+ """
+ Provides a string representation of the environments.
+
+ Returns:
+ str: A representation of the environments, including its name.
+ """
+ return f'{self.name}'
+
+ def __str__(self):
+ """
+ Returns the string representation of the environments, mirroring `__repr__`.
+
+ Returns:
+ str: A string representation of the environments.
+ """
+ return self.__repr__()
+
+
+if __name__ == '__main__':
+ env = Env()
+ env.env_state = EnvState()
+ # result = env.observe()
diff --git a/friday/environment/py_env.py b/oscopilot/environments/py_env.py
similarity index 54%
rename from friday/environment/py_env.py
rename to oscopilot/environments/py_env.py
index 82aa10a..0607c0f 100644
--- a/friday/environment/py_env.py
+++ b/oscopilot/environments/py_env.py
@@ -1,26 +1,47 @@
from __future__ import annotations
import subprocess
-import os
-from friday.core.schema import EnvState
-from friday.environment.env import Env
+from oscopilot.utils.schema import EnvState
+from oscopilot.environments.env import Env
from tempfile import NamedTemporaryFile
class PythonEnv(Env):
- """Base class for all actions.
+ """
+ A base class representing a Python execution environments for actions.
- Args:
- description (str, optional): The description of the action. Defaults to
- None.
- name (str, optional): The name of the action. If None, the name will
- be class name. Defaults to None.
+ This class provides a structured way to execute Python code snippets within a
+ specified environments, handling the execution state and any outputs or errors
+ generated during the execution. It extends the `Env` class, adding Python-specific
+ execution capabilities.
"""
def __init__(self) -> None:
+ """
+ Initializes the Python environments with default values.
+ """
super().__init__()
self._name: str = self.__class__.__name__
def step(self, _command: str, args: list[str] | str = []) -> EnvState:
+ """
+ Executes a Python command in the environments and updates the environments state.
+
+ This method takes a Python command as input, executes it within the environments's
+ working directory, and captures the command's output, error, and final working directory.
+ It supports passing arguments to the command and handles execution errors gracefully.
+
+ Args:
+ _command (str): The Python command to execute.
+ args (list[str] | str, optional): Additional arguments for the command. Can be a list
+ of arguments or a space-separated string. Defaults to an empty list.
+
+ Returns:
+ EnvState: An object representing the state of the environments after command execution, including any results, errors, and the current working directory.
+
+ Note:
+ The method ensures the last line of the output is always the current working directory
+ to maintain accurate state tracking.
+ """
tmp_code_file = NamedTemporaryFile("w", dir=self.working_dir, suffix=".py", encoding="utf-8")
# Solving the issue of not being able to retrieve the current working directory of the last line of output
_command = _command.strip() + "\n" + "import os" + "\n" + "print(os.getcwd())"
@@ -54,10 +75,16 @@ class PythonEnv(Env):
return self.env_state
- def reset(self):
- self.working_dir = os.path.abspath(os.path.join(__file__, "..", "..", "..", "working_dir"))
-
def observe(self, pwd):
+ """
+ Updates the environments state based on the current working directory.
+
+ This method sets the environments's working directory and lists its contents,
+ updating the `EnvState` object to reflect the current state of the environments.
+
+ Args:
+ pwd (str): The path to set as the current working directory.
+ """
self.env_state.pwd = pwd
self.working_dir = pwd
self.env_state.ls = subprocess.run(['ls'], cwd=self.working_dir, capture_output=True, text=True).stdout
diff --git a/oscopilot/modules/__init__.py b/oscopilot/modules/__init__.py
new file mode 100644
index 0000000..89f1cf6
--- /dev/null
+++ b/oscopilot/modules/__init__.py
@@ -0,0 +1,4 @@
+from .executor import *
+from .planner import *
+from .retriever import *
+from .learner import *
\ No newline at end of file
diff --git a/oscopilot/modules/base_module.py b/oscopilot/modules/base_module.py
new file mode 100644
index 0000000..0bd3c5b
--- /dev/null
+++ b/oscopilot/modules/base_module.py
@@ -0,0 +1,70 @@
+import re
+import json
+from oscopilot.utils.llms import OpenAI
+from oscopilot.environments.py_env import PythonEnv
+from oscopilot.tool_repository.basic_tools.get_os_version import get_os_version
+
+class BaseModule:
+ def __init__(self):
+ """
+ Initializes a new instance of BaseModule with default values for its attributes.
+ """
+ self.llm = OpenAI()
+ self.environment = PythonEnv()
+ self.system_version = get_os_version()
+
+ def extract_information(self, message, begin_str='[BEGIN]', end_str='[END]'):
+ """
+ Extracts substrings from a message that are enclosed within specified begin and end markers.
+
+ Args:
+ message (str): The message from which information is to be extracted.
+ begin_str (str): The marker indicating the start of the information to be extracted.
+ end_str (str): The marker indicating the end of the information to be extracted.
+
+ Returns:
+ list[str]: A list of extracted substrings found between the begin and end markers.
+ """
+ result = []
+ _begin = message.find(begin_str)
+ _end = message.find(end_str)
+ while not (_begin == -1 or _end == -1):
+ result.append(message[_begin + len(begin_str):_end])
+ message = message[_end + len(end_str):]
+ _begin = message.find(begin_str)
+ _end = message.find(end_str)
+ return result
+
+ def extract_json_from_string(self, text):
+ """
+ Identifies and extracts JSON data embedded within a given string.
+
+ This method searches for JSON data within a string, specifically looking for
+ JSON blocks that are marked with ```json``` notation. It attempts to parse
+ and return the first JSON object found.
+
+ Args:
+ text (str): The text containing the JSON data to be extracted.
+
+ Returns:
+ dict: The parsed JSON data as a dictionary if successful.
+ str: An error message indicating a parsing error or that no JSON data was found.
+ """
+ # Improved regular expression to find JSON data within a string
+ json_regex = r'```json\s*\n\{[\s\S]*?\n\}\s*```'
+
+ # Search for JSON data in the text
+ matches = re.findall(json_regex, text)
+
+ # Extract and parse the JSON data if found
+ if matches:
+ # Removing the ```json and ``` from the match to parse it as JSON
+ json_data = matches[0].replace('```json', '').replace('```', '').strip()
+ try:
+ # Parse the JSON data
+ parsed_json = json.loads(json_data)
+ return parsed_json
+ except json.JSONDecodeError as e:
+ return f"Error parsing JSON data: {e}"
+ else:
+ return "No JSON data found in the string."
\ No newline at end of file
diff --git a/oscopilot/modules/executor/__init__.py b/oscopilot/modules/executor/__init__.py
new file mode 100644
index 0000000..ad598bb
--- /dev/null
+++ b/oscopilot/modules/executor/__init__.py
@@ -0,0 +1 @@
+from .friday_executor import *
\ No newline at end of file
diff --git a/oscopilot/modules/executor/friday_executor.py b/oscopilot/modules/executor/friday_executor.py
new file mode 100644
index 0000000..f934f55
--- /dev/null
+++ b/oscopilot/modules/executor/friday_executor.py
@@ -0,0 +1,502 @@
+from oscopilot.modules.base_module import BaseModule
+from oscopilot.tool_repository.manager.tool_manager import get_open_api_doc_path
+import re
+import json
+from pathlib import Path
+from oscopilot.utils.utils import send_chat_prompts
+
+
+
+class FridayExecutor(BaseModule):
+ """
+ A modules within the system responsible for executing tools based on prompts and maintaining the tool library.
+
+ The ExecutionModule extends the BaseAgent class, focusing on the practical execution of tools determined
+ by the system. It utilizes a language learning model (LLM) in conjunction with an execution environments and
+ an tool library to carry out tools. Additionally, it manages system versioning and prompts initialization
+ for tool execution guidance.
+ """
+
+ def __init__(self, prompt, tool_manager, max_iter=3):
+ super().__init__()
+ self.prompt = prompt
+ self.tool_manager = tool_manager
+ self.max_iter = max_iter
+ self.open_api_doc_path = get_open_api_doc_path()
+ self.open_api_doc = {}
+ with open(self.open_api_doc_path) as f:
+ self.open_api_doc = json.load(f)
+
+ def generate_tool(self, task_name, task_description, pre_tasks_info, relevant_code):
+ """
+ Generates executable code and invocation logic for a specified tool.
+
+ This method constructs a message to generate tool code capable of completing the specified task,
+ taking into account any prerequisite task information and relevant code snippets. It then formats
+ this message for processing by the language learning model (LLM) to generate the tool code. The
+ method extracts the executable Python code and the specific invocation logic from the LLM's response.
+
+ Args:
+ task_name (str): The name of the task for which tool code is being generated.
+ task_description (str): A description of the task, detailing what the tool aims to accomplish.
+ pre_tasks_info (dict): Information about tasks that are prerequisites for the current task, including their descriptions and return values.
+ relevant_code (dict): A dictionary of code snippets relevant to the current task, possibly including code from prerequisite tasks.
+
+ Returns:
+ tuple: A tuple containing two elements:
+ - code (str): The generated Python code for the tool.
+ - invoke (str): The specific logic or command to invoke the generated tool.
+ """
+ relevant_code = json.dumps(relevant_code)
+
+ sys_prompt = self.prompt['_SYSTEM_SKILL_CREATE_AND_INVOKE_PROMPT']
+ user_prompt = self.prompt['_USER_SKILL_CREATE_AND_INVOKE_PROMPT'].format(
+ system_version=self.system_version,
+ task_description=task_description,
+ working_dir= self.environment.working_dir,
+ task_name=task_name,
+ pre_tasks_info=pre_tasks_info,
+ relevant_code=relevant_code
+ )
+
+ create_msg = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ code = self.extract_python_code(create_msg)
+ invoke = self.extract_information(create_msg, begin_str='', end_str=' ')[0]
+ return code, invoke
+
+ def execute_tool(self, code, invoke, node_type):
+ """
+ Executes a given tool code and returns the execution state.
+
+ This method handles the execution of tool code based on its node_type. For code tools, it appends
+ additional instructions to print the execution result within designated markers. It then passes
+ the modified code for execution in the environments. The method captures and prints the execution
+ state, including any results or errors, and returns this state.
+
+ Args:
+ code (str): The Python code to be executed as part of the tool.
+ invoke (str): The specific command or function call that triggers the tool within the code.
+ node_type (str): The type of the tool, determining how the tool is executed. Currently supports 'Code' type.
+
+ Returns:
+ state: The state object returned by the environments after executing the tool. This object contains
+ details about the execution's outcome, including any results or errors.
+
+ Note:
+ The execution logic is currently tailored for tools of type 'Code', where the code is directly executable
+ Python code. The method is designed to be extensible for other tool types as needed.
+ """
+ # print result info
+ if node_type == 'Code':
+ info = "\n" + '''print("")''' + "\n" + "print(result)" + "\n" + '''print(" ")'''
+ code = code + '\nresult=' + invoke + info
+ print("**************************************************")
+ print(code)
+ print("*************************************************")
+ state = self.environment.step(code)
+ print("**************************************************")
+ print(state)
+ # print("error: " + state.error + "\nresult: " + state.result + "\npwd: " + state.pwd + "\nls: " + state.ls)
+ print("************************ *************************")
+ return state
+
+ def judge_tool(self, code, task_description, state, next_action):
+ """
+ Evaluates the outcome of an executed tool to determine its success in completing a task.
+
+ This method formulates and sends a judgment request to the language learning model (LLM) based on the
+ executed tool's code, the task description, the execution state, and the expected next tool. It
+ then parses the LLM's response to determine the tool's success, providing reasoning, a judgment (boolean),
+ and a score that quantifies the tool's effectiveness.
+
+ Args:
+ code (str): The code of the tool that was executed.
+ task_description (str): The description of the task the tool was intended to complete.
+ state: The state object returned by the environments after executing the tool, containing execution results.
+ next_action (str): The name of the next expected tool in the sequence.
+
+ Returns:
+ tuple: A tuple containing:
+ - reasoning (str): The LLM's reasoning behind the judgment.
+ - judge (bool): The LLM's judgment on whether the tool successfully completed the task.
+ - score (float): A score representing the effectiveness of the tool.
+ """
+ next_action = json.dumps(next_action)
+ sys_prompt = self.prompt['_SYSTEM_TASK_JUDGE_PROMPT']
+ user_prompt = self.prompt['_USER_TASK_JUDGE_PROMPT'].format(
+ current_code=code,
+ task=task_description,
+ code_output=state.result,
+ current_working_dir=state.pwd,
+ working_dir=self.environment.working_dir,
+ files_and_folders=state.ls,
+ next_action=next_action
+ )
+ response = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ judge_json = self.extract_json_from_string(response)
+ print("**************************************************")
+ print(judge_json)
+ print("************************ *************************")
+ reasoning = judge_json['reasoning']
+ judge = judge_json['judge']
+ score = judge_json['score']
+ return reasoning, judge, score
+
+ def repair_tool(self, current_code, task_description, state, critique, pre_tasks_info):
+ """
+ Modifies or corrects the code of an tool based on feedback to better complete a task.
+
+ This method sends an amendment request to the LLM, including details about the current code, task description,
+ execution state, critique of the tool's outcome, and information about prerequisite tasks. It aims to generate
+ a revised version of the code that addresses any identified issues or incomplete aspects of the task. The method
+ extracts and returns both the amended code and the specific logic or command to invoke the amended tool.
+
+ Args:
+ current_code (str): The original code of the tool that requires amendment.
+ task_description (str): The description of the task the tool is intended to complete.
+ state: The state object containing details about the tool's execution outcome.
+ critique (str): Feedback or critique on the tool's execution, used to guide the amendment.
+ pre_tasks_info (dict): Information about tasks that are prerequisites for the current task.
+
+ Returns:
+ tuple: A tuple containing:
+ - new_code (str): The amended code for the tool.
+ - invoke (str): The command or logic to invoke the amended tool.
+ """
+ sys_prompt = self.prompt['_SYSTEM_SKILL_AMEND_AND_INVOKE_PROMPT']
+ user_prompt = self.prompt['_USER_SKILL_AMEND_AND_INVOKE_PROMPT'].format(
+ original_code = current_code,
+ task = task_description,
+ error = state.error,
+ code_output = state.result,
+ current_working_dir = state.pwd,
+ working_dir= self.environment.working_dir,
+ files_and_folders = state.ls,
+ critique = critique,
+ pre_tasks_info = pre_tasks_info
+ )
+ amend_msg = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ new_code = self.extract_python_code(amend_msg)
+ invoke = self.extract_information(amend_msg, begin_str='', end_str=' ')[0]
+ return new_code, invoke
+
+ def analysis_tool(self, code, task_description, state):
+ """
+ Analyzes the execution outcome of an tool to determine the nature of any errors.
+
+ This method evaluates the execution state of an tool, specifically looking for errors. Based on the
+ analysis, it determines whether the error is environmental and requires new operations (handled by the
+ planning modules) or is amendable via the `repair_tool` method. The analysis results, including the reasoning
+ and error type, are returned in JSON format.
+
+ Args:
+ code (str): The code that was executed for the tool.
+ task_description (str): The description of the task associated with the tool.
+ state: The state object containing the result of the tool's execution, including any errors.
+
+ Returns:
+ tuple: A tuple containing:
+ - reasoning (str): The analysis's reasoning regarding the nature of the error.
+ - type (str): The type of error identified ('environmental' for new operations, 'amendable' for corrections).
+ """
+ sys_prompt = self.prompt['_SYSTEM_ERROR_ANALYSIS_PROMPT']
+ user_prompt = self.prompt['_USER_ERROR_ANALYSIS_PROMPT'].format(
+ current_code=code,
+ task=task_description,
+ code_error=state.error,
+ current_working_dir=state.pwd,
+ working_dir= self.environment.working_dir,
+ files_and_folders= state.ls
+ )
+
+ response = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ analysis_json = self.extract_json_from_string(response)
+ print("**************************************************")
+ print(analysis_json)
+ print("************************ *************************")
+
+ reasoning = analysis_json['reasoning']
+ error_type = analysis_json['type']
+ return reasoning, error_type
+
+ def store_tool(self, tool, code):
+ """
+ Stores the provided tool and its code in the tool library.
+
+ If the specified tool does not already exist in the tool library, this method proceeds to store the tool's
+ code, arguments description, and other relevant information. It involves saving these details into JSON files and
+ updating the tool library database. If the tool already exists, it outputs a notification indicating so.
+
+ Args:
+ tool (str): The name of the tool to be stored.
+ code (str): The executable code associated with the tool.
+
+ Side Effects:
+ - Adds a new tool to the tool library if it doesn't already exist.
+ - Saves tool details to the filesystem and updates the tool library's database.
+ - Outputs a message if the tool already exists in the library.
+ """
+ # If tool not in db.
+ if not self.tool_manager.exist_tool(tool):
+ # Implement tool storage logic and store new tools
+ # args_description = self.extract_args_description(code)
+ tool_description = self.extract_tool_description(code)
+ # Save tool name, code, and description to JSON
+ tool_info = self.save_tool_info_to_json(tool, code, tool_description)
+ # Save code and descriptions to databases and JSON files
+ self.tool_manager.add_new_tool(tool_info)
+ # # Parameter description save path
+ # args_description_file_path = self.tool_manager.generated_tool_repo_dir + '/args_description/' + tool + '.txt'
+ # # save args_description
+ # self.save_str_to_path(args_description, args_description_file_path)
+ else:
+ print("tool already exists!")
+
+ def api_tool(self, description, api_path, context="No context provided."):
+ """
+ Executes a task by calling an API tool with the provided description and context.
+
+ This method formats a message to generate executable code for an API call based on the
+ provided description and context. It sends this message to the language learning model (LLM),
+ extracts the executable Python code from the LLM's response, and returns this code.
+
+ Args:
+ description (str): A description of the task to be performed by the API call.
+ api_path (str): The path or endpoint of the API to be called.
+ context (str, optional): Additional context to be included in the API call. Defaults to "No context provided.".
+
+ Returns:
+ str: The generated Python code to execute the API call.
+ """
+ self.sys_prompt = self.prompt['_SYSTEM_TOOL_USAGE_PROMPT'].format(
+ openapi_doc = json.dumps(self.generate_openapi_doc(api_path)),
+ tool_sub_task = description,
+ context = context
+ )
+ self.user_prompt = self.prompt['_USER_TOOL_USAGE_PROMPT']
+ response = send_chat_prompts(self.sys_prompt, self.user_prompt, self.llm)
+ code = self.extract_python_code(response)
+ return code
+
+ def question_and_answer_tool(self, context, question, current_question=None):
+ sys_prompt = self.prompt['_SYSTEM_QA_PROMPT']
+ user_prompt = self.prompt['_USER_QA_PROMPT'].format(
+ context = context,
+ question = question,
+ current_question = current_question
+ )
+ return send_chat_prompts(sys_prompt, user_prompt, self.llm)
+
+ def extract_python_code(self, response):
+ """
+ Extracts Python code snippets from a response string that includes code block markers.
+
+ This method parses a response string to extract Python code enclosed within '```python' and '```' markers.
+ It's designed to retrieve executable Python code snippets from formatted responses, such as those returned
+ by a language learning model after processing a code generation or analysis prompts.
+
+ Args:
+ response (str): The response string containing the Python code block to be extracted.
+
+ Returns:
+ str: The extracted Python code snippet, or an empty string if no code block is found.
+ """
+ python_code = ""
+ if '```python' in response:
+ python_code = response.split('```python')[1].split('```')[0]
+ elif '```' in python_code:
+ python_code = response.split('```')[1].split('```')[0]
+ return python_code
+
+ def extract_class_name_and_args_description(self, class_code):
+ """
+ Extracts the class name and arguments description from a given Python class code.
+
+ This method searches the provided class code for the class name and the documentation string
+ of the `__call__` method, which typically includes descriptions of the arguments. It uses regular
+ expressions to locate these elements within the code.
+
+ Args:
+ class_code (str): The Python code of the class from which information is to be extracted.
+
+ Returns:
+ tuple: A tuple containing:
+ - class_name (str): The name of the class extracted from the code.
+ - args_description (str): The arguments description extracted from the `__call__` method's docstring, if available; otherwise, None.
+ """
+ class_name_pattern = r"class (\w+)"
+ class_name_match = re.search(class_name_pattern, class_code)
+ class_name = class_name_match.group(1) if class_name_match else None
+
+ # Extracting the __call__ method's docstring
+ call_method_docstring_pattern = r"def __call__\([^)]*\):\s+\"\"\"(.*?)\"\"\""
+ call_method_docstring_match = re.search(call_method_docstring_pattern, class_code, re.DOTALL)
+ args_description = call_method_docstring_match.group(1).strip() if call_method_docstring_match else None
+
+ return class_name, args_description
+
+ def extract_args_description(self, class_code):
+ """
+ Extracts the arguments description from the `__call__` method's docstring within Python class code.
+
+ This method specifically targets the docstring of the `__call__` method in a class, which is conventionally
+ used to describe the method's parameters. The extraction is performed using a regular expression that
+ captures the content of the docstring.
+
+ Args:
+ class_code (str): The Python code of the class from which the arguments description is to be extracted.
+
+ Returns:
+ str: The extracted arguments description from the `__call__` method's docstring, or None if the docstring is not found or does not contain descriptions.
+ """
+ # Extracting the __call__ method's docstring
+ call_method_docstring_pattern = r"def __call__\([^)]*\):\s+\"\"\"(.*?)\"\"\""
+ call_method_docstring_match = re.search(call_method_docstring_pattern, class_code, re.DOTALL)
+ args_description = call_method_docstring_match.group(1).strip() if call_method_docstring_match else None
+ return args_description
+
+ def extract_tool_description(self, class_code):
+ """
+ Extracts the description of an tool from the class's initialization method in Python code.
+
+ This method looks for the tool's description assigned to `self._description` within the `__init__` method
+ of a class. It uses regular expressions to find this assignment and extracts the description string. This
+ approach assumes that the tool's description is directly assigned as a string literal to `self._description`.
+
+ Args:
+ class_code (str): The complete Python code of the class from which the tool description is to be extracted.
+
+ Returns:
+ str: The extracted description of the tool if found; otherwise, None.
+ """
+ init_pattern = r"def __init__\s*\(self[^)]*\):\s*(?:.|\n)*?self\._description\s*=\s*\"([^\"]+)\""
+ tool_match = re.search(init_pattern, class_code, re.DOTALL)
+ tool_description = tool_match.group(1).strip() if tool_match else None
+ return tool_description
+
+ def save_str_to_path(self, content, path):
+ """
+ Saves a string content to a file at the specified path, ensuring the directory exists.
+
+ This method takes a string and a file path, creating any necessary parent directories before
+ writing the content to the file. It ensures that the content is written with proper encoding and
+ that any existing content in the file is overwritten. The content is processed to remove extra
+ whitespace at the beginning and end of each line before saving.
+
+ Args:
+ content (str): The string content to be saved to the file.
+ path (str): The filesystem path where the content should be saved. If the directory does not exist,
+ it will be created.
+
+ Side Effects:
+ - Creates the directory path if it does not exist.
+ - Writes the content to a file at the specified path, potentially overwriting existing content.
+ """
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
+ with open(path, 'w', encoding='utf-8') as f:
+ lines = content.strip().splitlines()
+ content = '\n'.join(lines)
+ f.write(content)
+
+ def save_tool_info_to_json(self, tool, code, description):
+ """
+ Constructs a dictionary containing tool information suitable for JSON serialization.
+
+ This method packages the name, code, and description of an tool into a dictionary, making it ready
+ for serialization or further processing. This structured format is useful for saving tool details
+ in a consistent manner, facilitating easy storage and retrieval.
+
+ Args:
+ tool (str): The name of the tool.
+ code (str): The executable code associated with the tool.
+ description (str): A textual description of what the tool does.
+
+ Returns:
+ dict: A dictionary containing the tool's name, code, and description.
+ """
+ info = {
+ "task_name" : tool,
+ "code": code,
+ "description": description
+ }
+ return info
+
+ def extract_API_Path(self, text):
+ """
+ Extracts both UNIX-style and Windows-style file paths from the provided text string.
+
+ This method applies regular expressions to identify and extract file paths that may be present in
+ the input text. It is capable of recognizing paths that are enclosed within single or double quotes
+ and supports both UNIX-style paths (e.g., `/home/user/docs`) and Windows-style paths (e.g., `C:\\Users\\user\\docs`).
+ If multiple paths are found, only the first match is returned, following the function's current implementation.
+
+ Args:
+ text (str): The string from which file paths are to be extracted.
+
+ Returns:
+ str: The first file path found in the input text, with any enclosing quotes removed. If no paths are
+ found, an empty string is returned.
+
+ Note:
+ The current implementation returns only the first extracted path. If multiple paths are present in the
+ input text, consider modifying the method to return all found paths if the use case requires it.
+ """
+ # Regular expression for UNIX-style and Windows-style paths
+ unix_path_pattern = r"/[^/\s]+(?:/[^/\s]*)*"
+ windows_path_pattern = r"[a-zA-Z]:\\(?:[^\\\/\s]+\\)*[^\\\/\s]+"
+
+ # Combine both patterns
+ pattern = f"({unix_path_pattern})|({windows_path_pattern})"
+
+ # Find all matches
+ matches = re.findall(pattern, text)
+
+ # Extract paths from the tuples returned by findall
+ paths = [match[0] or match[1] for match in matches]
+
+ # Remove enclosing quotes (single or double) from the paths
+ stripped_paths = [path.strip("'\"") for path in paths]
+ return stripped_paths[0]
+
+
+ def generate_openapi_doc(self, tool_api_path):
+ """
+ Format openapi document.
+ """
+ # init current api's doc
+ curr_api_doc = {}
+ curr_api_doc["openapi"] = self.open_api_doc["openapi"]
+ curr_api_doc["info"] = self.open_api_doc["info"]
+ curr_api_doc["paths"] = {}
+ curr_api_doc["components"] = {"schemas":{}}
+ api_path_doc = {}
+ #extract path and schema
+ if tool_api_path not in self.open_api_doc["paths"]:
+ curr_api_doc = {"error": "The api is not existed"}
+ return curr_api_doc
+ api_path_doc = self.open_api_doc["paths"][tool_api_path]
+ curr_api_doc["paths"][tool_api_path] = api_path_doc
+ find_ptr = {}
+ if "get" in api_path_doc:
+ findptr = api_path_doc["get"]
+ elif "post" in api_path_doc:
+ findptr = api_path_doc["post"]
+ api_params_schema_ref = ""
+ # json格式
+ if (("requestBody" in findptr) and
+ ("content" in findptr["requestBody"]) and
+ ("application/json" in findptr["requestBody"]["content"]) and
+ ("schema" in findptr["requestBody"]["content"]["application/json"]) and
+ ("$ref" in findptr["requestBody"]["content"]["application/json"]["schema"])):
+ api_params_schema_ref = findptr["requestBody"]["content"]["application/json"]["schema"]["$ref"]
+ elif (("requestBody" in findptr) and
+ ("content" in findptr["requestBody"]) and
+ ("multipart/form-data" in findptr["requestBody"]["content"]) and
+ ("schema" in findptr["requestBody"]["content"]["multipart/form-data"]) and
+ ("allOf" in findptr["requestBody"]["content"]["multipart/form-data"]["schema"]) and
+ ("$ref" in findptr["requestBody"]["content"]["multipart/form-data"]["schema"]["allOf"][0])):
+ api_params_schema_ref = findptr["requestBody"]["content"]["multipart/form-data"]["schema"]["allOf"][0]["$ref"]
+ if api_params_schema_ref != None and api_params_schema_ref != "":
+ curr_api_doc["components"]["schemas"][api_params_schema_ref.split('/')[-1]] = self.open_api_doc["components"]["schemas"][api_params_schema_ref.split('/')[-1]]
+ return curr_api_doc
+
diff --git a/oscopilot/modules/learner/__init__.py b/oscopilot/modules/learner/__init__.py
new file mode 100644
index 0000000..044171e
--- /dev/null
+++ b/oscopilot/modules/learner/__init__.py
@@ -0,0 +1 @@
+from .self_learner import *
\ No newline at end of file
diff --git a/oscopilot/modules/learner/self_learner.py b/oscopilot/modules/learner/self_learner.py
new file mode 100644
index 0000000..631548b
--- /dev/null
+++ b/oscopilot/modules/learner/self_learner.py
@@ -0,0 +1,26 @@
+from oscopilot.modules.base_module import BaseModule
+from oscopilot.utils.utils import send_chat_prompts
+
+
+class SelfLearner(BaseModule):
+ def __init__(self, prompt, tool_manager):
+ super().__init__()
+ self.prompt = prompt
+ self.tool_manager = tool_manager
+ self.course = {}
+
+ def design_course(self, software_name, package_name, demo_file_path, file_content):
+ sys_prompt = self.prompt['_SYSTEM_COURSE_DESIGN_PROMPT']
+ user_prompt = self.prompt['_USER_COURSE_DESIGN_PROMPT'].format(
+ system_version = self.system_version,
+ software_name = software_name,
+ package_name = package_name,
+ file_content = file_content,
+ demo_file_path = demo_file_path
+ )
+ response = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ # logging.info(f"The overall response is: {response}")
+ course = self.extract_json_from_string(response)
+ self.course = course
+ return self.course
+
\ No newline at end of file
diff --git a/oscopilot/modules/planner/__init__.py b/oscopilot/modules/planner/__init__.py
new file mode 100644
index 0000000..04a7dba
--- /dev/null
+++ b/oscopilot/modules/planner/__init__.py
@@ -0,0 +1 @@
+from .friday_planner import *
\ No newline at end of file
diff --git a/oscopilot/modules/planner/friday_planner.py b/oscopilot/modules/planner/friday_planner.py
new file mode 100644
index 0000000..126dd4d
--- /dev/null
+++ b/oscopilot/modules/planner/friday_planner.py
@@ -0,0 +1,303 @@
+from oscopilot.tool_repository.manager.action_node import ActionNode
+from collections import defaultdict, deque
+from oscopilot.modules.base_module import BaseModule
+from oscopilot.tool_repository.manager.tool_manager import get_open_api_description_pair
+from oscopilot.utils.utils import send_chat_prompts
+import json
+import logging
+
+
+class FridayPlanner(BaseModule):
+ """
+ A planning module responsible for decomposing complex tasks into manageable subtasks, replanning tasks based on new insights or failures, and managing the execution order of tasks.
+
+ The `FridayPlanner` uses a combination of tool descriptions, environmental state, and language learning models to dynamically create and adjust plans for task execution. It maintains a tool graph to manage task dependencies and execution order, ensuring that tasks are executed in a sequence that respects their interdependencies.
+ """
+ def __init__(self, prompt):
+ super().__init__()
+ self.tool_num = 0
+ self.tool_node = {}
+ self.prompt = prompt
+ self.tool_graph = defaultdict(list)
+ self.sub_task_list = []
+
+ def reset_plan(self):
+ """
+ Resets the tool graph and subtask list to their initial states.
+ """
+ self.tool_num = 0
+ self.tool_node = {}
+ self.tool_graph = defaultdict(list)
+ self.sub_task_list = []
+
+ def decompose_task(self, task, tool_description_pair):
+ """
+ Decomposes a complex task into manageable subtasks and updates the tool graph.
+
+ This method takes a high-level task and an tool-description pair, and utilizes
+ the environments's current state to format and send a decomposition request to the
+ language learning model. It then parses the response to construct and update the
+ tool graph with the decomposed subtasks, followed by a topological sort to
+ determine the execution order.
+
+ Args:
+ task (str): The complex task to be decomposed.
+ tool_description_pair (dict): A dictionary mapping tool names to their descriptions.
+
+ Side Effects:
+ Updates the tool graph with the decomposed subtasks and reorders tools based on
+ dependencies through topological sorting.
+ """
+ files_and_folders = self.environment.list_working_dir()
+ tool_description_pair = json.dumps(tool_description_pair)
+ api_list = get_open_api_description_pair()
+ sys_prompt = self.prompt['_SYSTEM_TASK_DECOMPOSE_PROMPT']
+ user_prompt = self.prompt['_USER_TASK_DECOMPOSE_PROMPT'].format(
+ system_version=self.system_version,
+ task=task,
+ tool_list = tool_description_pair,
+ api_list = api_list,
+ working_dir = self.environment.working_dir,
+ files_and_folders = files_and_folders
+ )
+ response = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ decompose_json = self.extract_json_from_string(response)
+ # Building tool graph and topological ordering of tools
+ self.create_tool_graph(decompose_json)
+ self.topological_sort()
+
+ def replan_task(self, reasoning, current_task, relevant_tool_description_pair):
+ """
+ Replans the current task by integrating new tools into the original tool graph.
+
+ Given the reasoning for replanning and the current task, this method generates a new
+ tool plan incorporating any relevant tools. It formats a replanning request, sends
+ it to the language learning model, and integrates the response (new tools) into the
+ existing tool graph. The graph is then updated to reflect the new dependencies and
+ re-sorted topologically.
+
+ Args:
+ reasoning (str): The reasoning or justification for replanning the task.
+ current_task (str): The identifier of the current task being replanned.
+ relevant_tool_description_pair (dict): A dictionary mapping relevant tool names to
+ their descriptions for replanning.
+
+ Side Effects:
+ Modifies the tool graph to include new tools and updates the execution order
+ of tools within the graph.
+ """
+ # current_task information
+ current_tool = self.tool_node[current_task]
+ current_task_description = current_tool.description
+ relevant_tool_description_pair = json.dumps(relevant_tool_description_pair)
+ files_and_folders = self.environment.list_working_dir()
+ sys_prompt = self.prompt['_SYSTEM_TASK_REPLAN_PROMPT']
+ user_prompt = self.prompt['_USER_TASK_REPLAN_PROMPT'].format(
+ current_task = current_task,
+ current_task_description = current_task_description,
+ system_version=self.system_version,
+ reasoning = reasoning,
+ tool_list = relevant_tool_description_pair,
+ working_dir = self.environment.working_dir,
+ files_and_folders = files_and_folders
+ )
+ response = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ new_tool = self.extract_json_from_string(response)
+ # add new tool to tool graph
+ self.add_new_tool(new_tool, current_task)
+ # update topological sort
+ self.topological_sort()
+
+ def update_tool(self, tool, return_val='', relevant_code=None, status=False, node_type='Code'):
+ """
+ Updates the specified tool's node information within the tool graph.
+
+ This method allows updating an tool's return value, relevant code, execution status,
+ and node_type. It is particularly useful for modifying tools' details after their execution
+ or during the replanning phase.
+
+ Args:
+ tool (str): The tool identifier whose details are to be updated.
+ return_val (str, optional): The return value of the tool. Default is an empty string.
+ relevant_code (str, optional): Any relevant code associated with the tool. Default is None.
+ status (bool, optional): The execution status of the tool. Default is False.
+ node_type (str, optional): The node_type of the tool (e.g., 'Code'). Default is 'Code'.
+
+ Side Effects:
+ Updates the information of the specified tool node within the tool graph.
+ """
+ if return_val:
+ if node_type=='Code':
+ return_val = self.extract_information(return_val, "", " ")
+ print("**************************************************")
+ logging.info(return_val)
+ print(return_val)
+ print("************************ *************************")
+ if return_val != 'None':
+ self.tool_node[tool]._return_val = return_val
+ if relevant_code:
+ self.tool_node[tool]._relevant_code = relevant_code
+ self.tool_node[tool]._status = status
+
+ def get_tool_list(self, relevant_tool=None):
+ """
+ Retrieves a list of all tools or a subset of relevant tools, including their names and descriptions.
+
+ This method fetches tool descriptions from the tool library. If a specific set of relevant tools
+ is provided, it filters the list to include only those tools. The resulting list (or the full list if
+ no relevant tools are specified) is then returned in JSON format.
+
+ Args:
+ relevant_tool (list, optional): A list of tool names to filter the returned tools by.
+ If None, all tools are included. Defaults to None.
+
+ Returns:
+ A JSON string representing a dictionary of tool names to their descriptions.
+ The dictionary includes either all tools from the library or only those specified as relevant.
+ """
+ tool_dict = self.tool_manager.descriptions
+ if not relevant_tool:
+ return json.dumps(tool_dict)
+ relevant_tool_dict = {tool : description for tool ,description in tool_dict.items() if tool in relevant_tool}
+ relevant_tool_list = json.dumps(relevant_tool_dict)
+ return relevant_tool_list
+
+ def create_tool_graph(self, decompose_json):
+ """
+ Constructs an tool graph based on dependencies specified in the given JSON.
+
+ This method takes a JSON object containing task information and dependencies,
+ and constructs an tool graph. Each task is added as a node in the graph, with
+ directed edges representing task dependencies. The method updates the class's
+ internal structures to reflect this graph, including tool nodes and their
+ relationships, as well as the overall number of tools.
+
+ Args:
+ decompose_json (dict): A JSON object where each key is an tool name, and the value
+ is a dictionary containing the tool's name, description,
+ type, and dependencies.
+
+ Side Effects:
+ Modifies the internal state by updating `tool_num`, `tool_node`, and `tool_graph`
+ to reflect the newly created tool graph.
+ """
+ for _, task_info in decompose_json.items():
+ self.tool_num += 1
+ task_name = task_info['name']
+ task_description = task_info['description']
+ task_type = task_info['type']
+ task_dependencies = task_info['dependencies']
+ self.tool_node[task_name] = ActionNode(task_name, task_description, task_type)
+ self.tool_graph[task_name] = task_dependencies
+ for pre_tool in self.tool_graph[task_name]:
+ self.tool_node[pre_tool].next_action[task_name] = task_description
+
+ def add_new_tool(self, new_task_json, current_task):
+ """
+ Incorporates a new tool into the existing tool graph based on its dependencies.
+
+ This method processes a JSON object representing a new task, including its name,
+ description, type, and dependencies, and adds it to the tool graph. It also updates
+ the tool nodes to reflect this new addition. Finally, it appends the last new task
+ to the list of dependencies for the specified current task.
+
+ Args:
+ new_task_json (dict): A JSON object containing the new task's details.
+ current_task (str): The name of the current task to which the new task's dependencies will be added.
+
+ Side Effects:
+ Updates the tool graph and nodes to include the new tool and its dependencies.
+ Modifies the dependencies of the current task to include the new tool.
+ """
+ for _, task_info in new_task_json.items():
+ self.tool_num += 1
+ task_name = task_info['name']
+ task_description = task_info['description']
+ task_type = task_info['type']
+ task_dependencies = task_info['dependencies']
+ self.tool_node[task_name] = ActionNode(task_name, task_description, task_type)
+ self.tool_graph[task_name] = task_dependencies
+ for pre_tool in self.tool_graph[task_name]:
+ self.tool_node[pre_tool].next_action[task_name] = task_description
+ last_new_task = list(new_task_json.keys())[-1]
+ self.tool_graph[current_task].append(last_new_task)
+
+ def topological_sort(self):
+ """
+ Generates a topological sort of the tool graph to determine the execution order.
+
+ This method applies a topological sorting algorithm to the current tool graph,
+ considering the status of each tool. It aims to identify an order in which tools
+ can be executed based on their dependencies, ensuring that all prerequisites are met
+ before an tool is executed. The sorting algorithm accounts for tools that have not
+ yet been executed to avoid cycles and ensure a valid execution order.
+
+ Side Effects:
+ Populates `sub_task_list` with the sorted order of tools to be executed if a
+ topological sort is possible. Otherwise, it indicates a cycle detection.
+ """
+ self.sub_task_list = []
+ graph = defaultdict(list)
+ for node, dependencies in self.tool_graph.items():
+ # If the current node has not been executed, put it in the dependency graph.
+ if not self.tool_node[node].status:
+ graph.setdefault(node, [])
+ for dependent in dependencies:
+ # If the dependencies of the current node have not been executed, put them in the dependency graph.
+ if not self.tool_node[dependent].status:
+ graph[dependent].append(node)
+
+ in_degree = {node: 0 for node in graph}
+ # Count in-degree for each node
+ for node in graph:
+ for dependent in graph[node]:
+ in_degree[dependent] += 1
+
+ # Initialize queue with nodes having in-degree 0
+ queue = deque([node for node in in_degree if in_degree[node] == 0])
+
+ # List to store the order of execution
+
+ while queue:
+ # Get one node with in-degree 0
+ current = queue.popleft()
+ self.sub_task_list.append(current)
+
+ # Decrease in-degree for all nodes dependent on current
+ for dependent in graph[current]:
+ in_degree[dependent] -= 1
+ if in_degree[dependent] == 0:
+ queue.append(dependent)
+
+ # Check if topological sort is possible (i.e., no cycle)
+ if len(self.sub_task_list) == len(graph):
+ print("topological sort is possible")
+ else:
+ return "Cycle detected in the graph, topological sort not possible."
+
+ def get_pre_tasks_info(self, current_task):
+ """
+ Retrieves information about the prerequisite tasks for a given current task.
+
+ This method collects and formats details about all tasks that are prerequisites
+ for the specified current task. It extracts descriptions and return values for
+ each prerequisite task and compiles this information into a JSON string.
+
+ Args:
+ current_task (str): The name of the task for which prerequisite information is requested.
+
+ Returns:
+ A JSON string representing a dictionary, where each key is a prerequisite task's
+ name, and the value is a dictionary with the task's description and return value.
+ """
+ pre_tasks_info = {}
+ for task in self.tool_graph[current_task]:
+ task_info = {
+ "description" : self.tool_node[task].description,
+ "return_val" : self.tool_node[task].return_val
+ }
+ pre_tasks_info[task] = task_info
+ pre_tasks_info = json.dumps(pre_tasks_info)
+ return pre_tasks_info
+
+
diff --git a/oscopilot/modules/retriever/__init__.py b/oscopilot/modules/retriever/__init__.py
new file mode 100644
index 0000000..0fdc6c0
--- /dev/null
+++ b/oscopilot/modules/retriever/__init__.py
@@ -0,0 +1 @@
+from .vector_retriever import *
\ No newline at end of file
diff --git a/oscopilot/modules/retriever/vector_retriever.py b/oscopilot/modules/retriever/vector_retriever.py
new file mode 100644
index 0000000..e14463a
--- /dev/null
+++ b/oscopilot/modules/retriever/vector_retriever.py
@@ -0,0 +1,153 @@
+from oscopilot.modules.base_module import BaseModule
+from oscopilot.utils.utils import send_chat_prompts
+import json
+
+
+class FridayRetriever(BaseModule):
+ """
+ A modules within the system responsible for retrieving and managing available tools from the tool library.
+
+ The RetrievalModule extends the BaseModule class, focusing on the retrieval of tools
+ based on specific prompts or queries. It interacts with a language learning model (LLM)
+ and utilizes the execution environments and tool library to fulfill its responsibilities.
+ """
+
+ def __init__(self, prompt, tool_manager):
+ super().__init__()
+ self.prompt = prompt
+ self.tool_manager = tool_manager
+
+ def delete_tool(self, tool):
+ """
+ Deletes the specified tool from the tool library.
+
+ This method calls the tool library's delete method to remove an tool by its name. It
+ encompasses deleting the tool's code, description, parameters, and any other associated
+ information.
+
+ Args:
+ tool (str): The name of the tool to be deleted.
+ """
+ self.tool_manager.delete_tool(tool)
+
+ def retrieve_tool_name(self, task, k=10):
+ """
+ Retrieves a list of tool names relevant to the specified task.
+
+ This method interacts with the tool library to retrieve names of tools that are most
+ relevant to a given task. The number of tool names returned is limited by the parameter k.
+
+ Args:
+ task (str): The task for which relevant tool names are to be retrieved.
+ k (int, optional): The maximum number of tool names to retrieve. Defaults to 10.
+
+ Returns:
+ list[str]: A list of the top k tool names relevant to the specified task.
+ """
+ retrieve_tool_name = self.tool_manager.retrieve_tool_name(task, k)
+ return retrieve_tool_name
+
+ def tool_code_filter(self, tool_code_pair, task):
+ """
+ Filters and retrieves the code for an tool relevant to the specified task.
+
+ This method formats a message for filtering tool codes based on a given task, sends
+ the message to the tool library for processing, and retrieves the filtered tool's
+ code. If an tool name is successfully identified, its corresponding code is fetched
+ from the tool library.
+
+ Args:
+ tool_code_pair (dict): A dictionary mapping tool names to their codes.
+ task (str): The task based on which the tool code needs to be filtered.
+
+ Returns:
+ The code of the tool relevant to the specified task, or an empty string
+ if no relevant tool is found.
+ """
+ tool_code_pair = json.dumps(tool_code_pair)
+ sys_prompt = self.prompt['_SYSTEM_ACTION_CODE_FILTER_PROMPT']
+ user_prompt = self.prompt['_USER_ACTION_CODE_FILTER_PROMPT'].format(
+ task_description=task,
+ tool_code_pair=tool_code_pair
+ )
+ response = send_chat_prompts(sys_prompt, user_prompt, self.llm)
+ tool_name = self.extract_information(response, '', ' ')[0]
+ code = ''
+ if tool_name:
+ code = self.tool_manager.get_tool_code(tool_name)
+ return code
+
+ def retrieve_tool_description(self, tool_name):
+ """
+ Retrieves the description for a specified tool from the tool library.
+
+ This method queries the tool library for the description of an tool identified
+ by its name. It is designed to fetch detailed descriptions that explain what the
+ tool does.
+
+ Args:
+ tool_name (str): The name of the tool whose description is to be retrieved.
+
+ Returns:
+ str: The description of the specified tool.
+ """
+ retrieve_tool_description = self.tool_manager.retrieve_tool_description(tool_name)
+ return retrieve_tool_description
+
+ def retrieve_tool_code(self, tool_name):
+ """
+ Retrieves the code for a specified tool from the tool library.
+
+ This method accesses the tool library to get the executable code associated with
+ an tool identified by its name. This code defines how the tool is performed.
+
+ Args:
+ tool_name (str): The name of the tool whose code is to be retrieved.
+
+ Returns:
+ str: The code of the specified tool.
+ """
+ retrieve_tool_code = self.tool_manager.retrieve_tool_code(tool_name)
+ return retrieve_tool_code
+
+ def retrieve_tool_code_pair(self, retrieve_tool_name):
+ """
+ Retrieves a mapping of tool names to their respective codes for a list of tools.
+
+ This method processes a list of tool names, retrieving the code for each and
+ compiling a dictionary that maps each tool name to its code. This is useful for
+ tasks that require both the identification and the execution details of tools.
+
+ Args:
+ retrieve_tool_name (list[str]): A list of tool names for which codes are to be retrieved.
+
+ Returns:
+ dict: A dictionary mapping each tool name to its code.
+ """
+ retrieve_tool_code = self.retrieve_tool_code(retrieve_tool_name)
+ tool_code_pair = {}
+ for name, description in zip(retrieve_tool_name, retrieve_tool_code):
+ tool_code_pair[name] = description
+ return tool_code_pair
+
+ def retrieve_tool_description_pair(self, retrieve_tool_name):
+ """
+ Retrieves a mapping of tool names to their descriptions for a list of tools.
+
+ By processing a list of tool names, this method fetches their descriptions and
+ forms a dictionary that associates each tool name with its description. This
+ facilitates understanding the purpose and functionality of multiple tools at once.
+
+ Args:
+ retrieve_tool_name (list[str]): A list of tool names for which descriptions are to be retrieved.
+
+ Returns:
+ dict: A dictionary mapping each tool name to its description.
+ """
+ retrieve_tool_description = self.retrieve_tool_description(retrieve_tool_name)
+ tool_description_pair = {}
+ for name, description in zip(retrieve_tool_name, retrieve_tool_description):
+ tool_description_pair[name] = description
+ return tool_description_pair
+
+
diff --git a/friday/action_lib/__init__.py b/oscopilot/prompts/__init__.py
similarity index 100%
rename from friday/action_lib/__init__.py
rename to oscopilot/prompts/__init__.py
diff --git a/friday/agent/prompt.py b/oscopilot/prompts/friday_pt.py
similarity index 69%
rename from friday/agent/prompt.py
rename to oscopilot/prompts/friday_pt.py
index a22ec4d..0d7b6b8 100644
--- a/friday/agent/prompt.py
+++ b/oscopilot/prompts/friday_pt.py
@@ -1,12 +1,34 @@
+"""
+This modules contains a comprehensive `prompts` dictionary that serves as a repository of prompts for guiding the AI agents's interactions across various operational scenarios, including execution, planning, and information retrieval tasks. These prompts are meticulously crafted to instruct the AI in performing its duties, ranging from code generation and amendment to task decomposition and planning, as well as error analysis and tool usage.
+
+The dictionary is segmented into three main categories:
+
+1. **execute_prompt**: Contains prompts for execution-related tasks, such as code generation, invocation, amendment, and error judgment. These are further detailed for system actions and user interactions, facilitating a diverse range of programming and troubleshooting tasks.
+
+2. **planning_prompt**: Focuses on task planning and re-planning, decomposing complex tasks into manageable sub-tasks, and adapting plans based on unforeseen issues, ensuring that the AI can assist in project management and task organization effectively.
+
+3. **retrieve_prompt**: Dedicated to information retrieval, including filtering code snippets based on specific criteria, aiding the AI in sourcing and suggesting code solutions efficiently.
+
+Each category comprises system and user prompts, where system prompts define the AI's task or query in detail, and user prompts typically include placeholders for dynamic information insertion, reflecting the context or specific requirements of the task at hand.
+
+Usage:
+The `prompts` dictionary is utilized by the AI agents to dynamically select appropriate prompts based on the current context or task, ensuring relevant and precise guidance for each operation. This dynamic approach allows the AI to adapt its interactions and responses to suit a wide array of programming and operational needs, enhancing its utility and effectiveness in assisting users.
+
+Example:
+ .. code-block:: python
+
+ # Accessing a specific prompts for task execution
+ execute_prompt = prompts['execute_prompt']['_SYSTEM_SKILL_CREATE_AND_INVOKE_PROMPT']
+"""
prompt = {
- 'execute_prompt' : {
- # Code generate and invoke prompt in os
+ 'execute_prompt': {
+ # Code generate and invoke prompts in os
'_SYSTEM_SKILL_CREATE_AND_INVOKE_PROMPT': '''
You are helpful assistant to assist in writing Python tool code for tasks completed on operating systems. Your expertise lies in creating Python classes that perform specific tasks, adhering to a predefined format and structure.
Your goal is to generate Python tool code in the form of a class. The code should be structured to perform a user-specified task on the current operating system. The class must be easy to use and understand, with clear instructions and comments.
You should only respond with a python code and a invocation statement.
Python code in the format as described below:
- 1. Code Structure: Begin with the necessary import statement: from friday.action.base_action import BaseAction. Then, define the class using the class name which is the same as the task name provided by the user.
+ 1. Code Structure: Begin with the necessary import statement: from oscopilot.tool_repository.basic_tools.base_action import BaseAction. Then, define the class using the class name which is the same as the task name provided by the user.
2. Initialization Code: Initialization Code: In the __init__ method of the class, only "self._description" is initialized. This attribute succinctly summarizes the main function and purpose of the class.
3. Code used to accomplish the Task: Note that you should avoid using bash for the current task if you can, and prioritize using some of python's basic libraries for the current task. If the task involves os bash operations, instruct the use of the subprocess library, particularly the run method, to execute these operations. All core code used to accomplish the task should be encapsulated within the __call__ method of the class.
4. Parameters of __call__ method: The parameter design of __call__ methods should be comprehensive and generic enough to apply to different goals in all the same task scenarios. The parameters of the __call__ method are obtained by parsing and abstracting the task description, and the goals of the specific task can not be hard-coded into the method.
@@ -17,8 +39,8 @@ prompt = {
3. Generating Invocation Statement: Construct the __call__ method invocation statement. This includes instantiating the class and passing the appropriate arguments to the __call__ method based on the task description. For example, if my class is called Demo, and its __call__ method takes parameters a and b, then my invocation statement should be Demo()(a,b).
4. Output Format: The final output should include the invocation statement, which must be enclosed in tags. For example, Demo()(a,b) .
And the code you write should also follow the following criteria:
- 1. The class must start with from friday.action.base_action import BaseAction.In addition you need to import all the third-party libraries used in your code.
- 2. The class name should be the same as the user's task name.
+ 1. The class must start with from oscopilot.tool_repository.basic_tools.base_action import BaseAction.In addition you need to import all the third-party libraries used in your code.
+ 2. If you reuse the code provided in the user's information, the class name should be consistent with the reused code's class name; otherwise, the class name should be the same as the user's task name.
3. In the __init__ method, only self._description should be initialized. And self._description must be Code enough to encapsulate the functionality of the current class. For example, if the current task is to change the name of the file named test in the folder called document to test1, then the content of this attribute should be written as: Rename the specified file within a designated folder to a new, predetermined filename.
4. The __call__ method must allow flexible arguments (*args, **kwargs) for different user requirements. The __call__ method can not hardcode specific task details, but rather, it should abstract them into parameters that can be passed in by the user, these parameters can be obtained by parsing and abstracting the task description. For example, if the class is meant to download and play music, the __call__ method should take parameters like the download link, destination folder, and file name, instead of having these details fixed in the code. Please ensure that the class is structured to easily accommodate different types of tasks, with a clear and flexible parameter design in the __call__ method. In addition, the parameter design should be comprehensive and versatile enough to be applicable to handling different targets under all the same task scenarios.
5. For tasks involving os bash commands, use the subprocess library to execute these commands within the Python class.
@@ -36,7 +58,7 @@ prompt = {
17. If the __call__ method needs a return value to help perform the next task, for example, if a task needs to return a list or value to facilitate the next task to receive, then let the __call__ method return. Otherwise, there is no need to return
18. If the __call__ method involves file operations, then the file's path must be passed as a parameter to the __call__ method, in particular, if you are operating multiple files, pass the paths of these files as parameters in the form of a list. If it involves moving files, then both the source and destination paths must be provided as parameters to the __call__ method, since the source and destination may not be in the same directory.
19. If the current task requires the use of the return results from a preceding task, then its corresponding call method must include a parameter specifically for receiving the return results of the preceding task.
- 20. Please note that I have provided you with some codes similar to the current task in the Relevant Code of the user information. If the current task can be directly implemented with a certain code, then use this code directly without modifying code.
+ 20. Please note that in the Relevant Code section of the user's information, I have provided some codes that may be capable of solving the current task. Please carefully review these codes. If there is a code that can directly solve the current task, please reuse it without making any modifications.
21. If the code involves the output of file paths, ensure that the output includes the files' absolute path.
22. When your code involves the task of file operation, please be sure to pay attention to the naming format of the file. If it is a jpg file called XXX, the name should be XXX.jpg. If it is an mp4 file called XXX, the name should be XXX.mp4. Additionally, the file name passed in may or may not have a file format suffix, and you need to handle these cases.
23. Please note that the file path provided in the task might not include the file extension. This does not necessarily mean that the path is for a folder. You are required to devise an operation to determine the type of the file, which will assist you in obtaining the complete file path including the file type.
@@ -45,14 +67,13 @@ prompt = {
And the invocation statement should also follow the following criteria:
1. The __call__ method invocation must be syntactically correct as per Python standards.
2. Clearly identify any fake or placeholder parameters used in the invocation.
- 3. If necessary, you can use the Working Directory provided by the user as a parameter passed into the __call__ method.
- 4. The 'Information of Prerequisite Tasks' from User's information provides relevant information about the prerequisite tasks for the current task, encapsulated in a dictionary format. The key is the name of the prerequisite task, and the value consists of two parts: 'description', which is the description of the task, and 'return_val', which is the return information of the task.
- 5. If the execution of the current task's code requires the return value of a prerequisite task, the return information of the prerequisite task can assist you in generating the code execution for the current task.
- 6. 'Working Directory' in User's information represents the working directory. It may not necessarily be the same as the current working directory. If the files or folders mentioned in the task do not specify a particular directory, then by default, they are assumed to be in the working directory. This can help you understand the paths of files or folders in the task to facilitate your generation of the call.
- 7. The code comments include an example of a class invocation. You can refer to this example, but you should not directly copy it. Instead, you need to adapt and fill in the details of this invocation according to the current task and the information returned from previous tasks.
- 8. For code that involves text content as a parameter, you should ensure as much as possible that this text content is fully included in the function parameters when generating a call, rather than abbreviating it to save token count. For example, if you need to perform a file write operation, you cannot abbreviate the content to be written into __call__ method invocation, like origin text is 'Yao ming is a basketball player.', you can not write 'Yao ming is ...'.
- 9. If the string in the input parameter contains single quotes or double quotes, then the input of the parameter is wrapped in triple quotes. The following is an example of an invocation statement: Demo()("""xx"x"xxx""" )
- 10. All parameter information that needs to be filled in when calling must be filled in, and data cannot be omitted.
+ 3. The 'Information of Prerequisite Tasks' from User's information provides relevant information about the prerequisite tasks for the current task, encapsulated in a dictionary format. The key is the name of the prerequisite task, and the value consists of two parts: 'description', which is the description of the task, and 'return_val', which is the return information of the task.
+ 4. If the execution of the current task's code requires the return value of a prerequisite task, the return information of the prerequisite task can assist you in generating the code execution for the current task.
+ 5. 'Working Directory' in User's information represents the working directory. It may not necessarily be the same as the current working directory. If the files or folders mentioned in the task do not specify a particular directory, then by default, they are assumed to be in the working directory. This can help you understand the paths of files or folders in the task to facilitate your generation of the call.
+ 6. The code comments include an example of a class invocation. You can refer to this example, but you should not directly copy it. Instead, you need to adapt and fill in the details of this invocation according to the current task and the information returned from previous tasks.
+ 7. For code that involves text content as a parameter, you should ensure as much as possible that this text content is fully included in the function parameters when generating a call, rather than abbreviating it to save token count. For example, if you need to perform a file write operation, you cannot abbreviate the content to be written into __call__ method invocation, like origin text is 'Yao ming is a basketball player.', you can not write 'Yao ming is ...'.
+ 8. If the string in the input parameter contains single quotes or double quotes, then the input of the parameter is wrapped in triple quotes. The following is an example of an invocation statement: Demo()("""xx"x"xxx""" )
+ 9. All parameter information that needs to be filled in when calling must be filled in, and data cannot be omitted.
Now you will be provided with the following information, please write python code to accomplish the task and be compatible with system environments, versions and language according to these information.
''',
'_USER_SKILL_CREATE_AND_INVOKE_PROMPT': '''
@@ -66,8 +87,8 @@ prompt = {
Relevant Code: {relevant_code}
''',
- # Invoke generate prompt in os
- '_SYSTEM_INVOKE_GENERATE_PROMPT' : '''
+ # Invoke generate prompts in os
+ '_SYSTEM_INVOKE_GENERATE_PROMPT': '''
You are an AI trained to assist with Python programming tasks, with a focus on class and method usage.
Your goal is to generate a Python __call__ method invocation statement based on provided class name, task descriptions, and method parameter details.
You should only respond with the python code in the format as described below:
@@ -88,7 +109,7 @@ prompt = {
8. The code comments include an example of a class invocation. You can refer to this example, but you should not directly copy it. Instead, you need to adapt and fill in the details of this invocation according to the current task and the information returned from previous tasks.
Now you will be provided with the following information, please generate your response according to these information:
''',
- '_USER_INVOKE_GENERATE_PROMPT' : '''
+ '_USER_INVOKE_GENERATE_PROMPT': '''
User's information are as follows:
Class Name: {class_name}
Task Description: {task_description}
@@ -97,8 +118,8 @@ prompt = {
Working Directory: {working_dir}
''',
- # Skill amend and invoke prompt in os
- '_SYSTEM_SKILL_AMEND_AND_INVOKE_PROMPT' : '''
+ # Skill amend and invoke prompts in os
+ '_SYSTEM_SKILL_AMEND_AND_INVOKE_PROMPT': '''
You are an AI expert in Python programming, with a focus on diagnosing and resolving code issues.
Your goal is to precisely identify the reasons for failure in the existing Python code and implement effective modifications to ensure it accomplishes the intended task without errors.
You should only respond with a python code and a invocation statement.
@@ -125,15 +146,14 @@ prompt = {
And the invocation statement should also follow the following criteria:
1. The __call__ method invocation must be syntactically correct as per Python standards.
2. Clearly identify any fake or placeholder parameters used in the invocation.
- 3. If necessary, you can use the Working Directory provided by the user as a parameter passed into the __call__ method.
- 4. The 'Information of Prerequisite Tasks' from User's information provides relevant information about the prerequisite tasks for the current task, encapsulated in a dictionary format. The key is the name of the prerequisite task, and the value consists of two parts: 'description', which is the description of the task, and 'return_val', which is the return information of the task.
- 5. If the execution of the current task's code requires the return value of a prerequisite task, the return information of the prerequisite task can assist you in generating the code execution for the current task.
- 6. 'Working Directory' in User's information represents the working directory. It may not necessarily be the same as the current working directory. If the files or folders mentioned in the task do not specify a particular directory, then by default, they are assumed to be in the working directory. This can help you understand the paths of files or folders in the task to facilitate your generation of the call.
- 7. The code comments include an example of a class invocation. You can refer to this example, but you should not directly copy it. Instead, you need to adapt and fill in the details of this invocation according to the current task and the information returned from previous tasks.
- 8. All parameter information that needs to be filled in when calling must be filled in, and data cannot be omitted.
+ 3. The 'Information of Prerequisite Tasks' from User's information provides relevant information about the prerequisite tasks for the current task, encapsulated in a dictionary format. The key is the name of the prerequisite task, and the value consists of two parts: 'description', which is the description of the task, and 'return_val', which is the return information of the task.
+ 4. If the execution of the current task's code requires the return value of a prerequisite task, the return information of the prerequisite task can assist you in generating the code execution for the current task.
+ 5. 'Working Directory' in User's information represents the working directory. It may not necessarily be the same as the current working directory. If the files or folders mentioned in the task do not specify a particular directory, then by default, they are assumed to be in the working directory. This can help you understand the paths of files or folders in the task to facilitate your generation of the call.
+ 6. The code comments include an example of a class invocation. You can refer to this example, but you should not directly copy it. Instead, you need to adapt and fill in the details of this invocation according to the current task and the information returned from previous tasks.
+ 7. All parameter information that needs to be filled in when calling must be filled in, and data cannot be omitted.
Now you will be provided with the following information, please give your modified python code and invocation statement according to these information:
''',
- '_USER_SKILL_AMEND_AND_INVOKE_PROMPT' : '''
+ '_USER_SKILL_AMEND_AND_INVOKE_PROMPT': '''
User's information are as follows:
Original Code: {original_code}
Task: {task}
@@ -146,9 +166,8 @@ prompt = {
Information of Prerequisite Tasks: {pre_tasks_info}
''',
-
- # Skill amend prompt in os
- '_SYSTEM_SKILL_AMEND_PROMPT' : '''
+ # Skill amend prompts in os
+ '_SYSTEM_SKILL_AMEND_PROMPT': '''
You are an AI expert in Python programming, with a focus on diagnosing and resolving code issues.
Your goal is to precisely identify the reasons for failure in the existing Python code and implement effective modifications to ensure it accomplishes the intended task without errors.
You should only respond with the python code in the format as described below:
@@ -168,7 +187,7 @@ prompt = {
10. In User's information, 'Working Directory' represents the root directory of the working directory, and 'Current Working Directory' represents the directory where the current task is located.
Now you will be provided with the following information, please give your modified python code according to these information:
''',
- '_USER_SKILL_AMEND_PROMPT' : '''
+ '_USER_SKILL_AMEND_PROMPT': '''
User's information are as follows:
Original Code: {original_code}
Task: {task}
@@ -180,18 +199,18 @@ prompt = {
Critique On The Code: {critique}
''',
- # Skill create prompt in os
- '_SYSTEM_SKILL_CREATE_PROMPT' : '''
+ # Skill create prompts in os
+ '_SYSTEM_SKILL_CREATE_PROMPT': '''
You are helpful assistant to assist in writing Python tool code for tasks completed on operating systems. Your expertise lies in creating Python classes that perform specific tasks, adhering to a predefined format and structure.
Your goal is to generate Python tool code in the form of a class. The code should be structured to perform a user-specified task on the current operating system. The class must be easy to use and understand, with clear instructions and comments.
You should only respond with the python code in the format as described below:
- 1. Code Structure: Begin with the necessary import statement: from friday.action.base_action import BaseAction. Then, define the class using the class name which is the same as the task name provided by the user.
+ 1. Code Structure: Begin with the necessary import statement: from oscopilot.tool_repository.basic_tools.base_action import BaseAction. Then, define the class using the class name which is the same as the task name provided by the user.
2. Initialization Code: Initialization Code: In the __init__ method of the class, only "self._description" is initialized. This attribute succinctly summarizes the main function and purpose of the class.
3. Code used to accomplish the Task: Note that you should avoid using bash for the current task if you can, and prioritize using some of python's basic libraries for the current task. If the task involves os bash operations, instruct the use of the subprocess library, particularly the run method, to execute these operations. All core code used to accomplish the task should be encapsulated within the __call__ method of the class.
4. Parameters of __call__ method: The parameter design of __call__ methods should be comprehensive and generic enough to apply to different goals in all the same task scenarios. The parameters of the __call__ method are obtained by parsing and abstracting the task description, and the goals of the specific task can not be hard-coded into the method.
5. Detailed Comments: Provide comprehensive comments throughout the code. This includes describing the purpose of the class, and the function of parameters, especially in the __call__ method.
And the code you write should also follow the following criteria:
- 1. The class must start with from friday.action.base_action import BaseAction.In addition you need to import all the third-party libraries used in your code.
+ 1. The class must start with from oscopilot.tool_repository.basic_tools.base_action import BaseAction.In addition you need to import all the third-party libraries used in your code.
2. The class name should be the same as the user's task name.
3. In the __init__ method, only self._description should be initialized. And self._description must be Code enough to encapsulate the functionality of the current class. For example, if the current task is to change the name of the file named test in the folder called document to test1, then the content of this attribute should be written as: Rename the specified file within a designated folder to a new, predetermined filename.
4. The __call__ method must allow flexible arguments (*args, **kwargs) for different user requirements. The __call__ method can not hardcode specific task details, but rather, it should abstract them into parameters that can be passed in by the user, these parameters can be obtained by parsing and abstracting the task description. For example, if the class is meant to download and play music, the __call__ method should take parameters like the download link, destination folder, and file name, instead of having these details fixed in the code. Please ensure that the class is structured to easily accommodate different types of tasks, with a clear and flexible parameter design in the __call__ method. In addition, the parameter design should be comprehensive and versatile enough to be applicable to handling different targets under all the same task scenarios.
@@ -212,7 +231,7 @@ prompt = {
19. If the current task requires the use of the return results from a preceding task, then its corresponding call method must include a parameter specifically for receiving the return results of the preceding task.
Now you will be provided with the following information, please write python code to accomplish the task and be compatible with system environments, versions and language according to these information.
''',
- '_USER_SKILL_CREATE_PROMPT' : '''
+ '_USER_SKILL_CREATE_PROMPT': '''
User's information is as follows:
System Version: {system_version}
System language: simplified chinese
@@ -221,8 +240,8 @@ prompt = {
Task Description: {task_description}
''',
- # Task judge prompt in os
- '_SYSTEM_TASK_JUDGE_PROMPT' : '''
+ # Task judge prompts in os
+ '_SYSTEM_TASK_JUDGE_PROMPT': '''
You are an AI program expert to verify Python code against a user's task requirements.
Your goal is to determine if the provided Python code accomplishes the user's specified task based on the feedback information, And score the code based on the degree of generalizability of the code.
You should only respond with the JSON result in the format as described below:
@@ -246,7 +265,7 @@ prompt = {
10. 'Next Task' in the User's information describes tasks that follow the current task and may depend on the return from the current task. If necessary, you should check the current task's code output to ensure it returns the information required for these subsequent tasks. If it does not, then the current task can be considered incomplete.
Now you will be provided with the following information, please give the result JSON according to these information:
''',
- '_USER_TASK_JUDGE_PROMPT' : '''
+ '_USER_TASK_JUDGE_PROMPT': '''
User's information are as follows:
Current Code: {current_code}
Task: {task}
@@ -257,10 +276,10 @@ prompt = {
Next Task: {next_action}
''',
- # Code error judge prompt in os
- '_SYSTEM_ERROR_ANALYSIS_PROMPT' : '''
+ # Code error judge prompts in os
+ '_SYSTEM_ERROR_ANALYSIS_PROMPT': '''
You are an expert in analyzing Python code errors, you are able to make an accurate analysis of different types of errors, and your return results adhere to a predefined format and structure.
- Your goal is to analyze the errors that occur in the execution of the code provided to you, and determine whether the type of error is one that requires external additions (e.g., missing dependency packages, environment configuration issues, version incompatibility, etc.) or one that only requires internal changes to the code (e.g., syntax errors, logic errors, data type errors).
+ Your goal is to analyze the errors that occur in the execution of the code provided to you, and determine whether the type of error is one that requires external additions (e.g., missing dependency packages, environments configuration issues, version incompatibility, etc.) or one that only requires internal changes to the code (e.g., syntax errors, logic errors, data type errors).
You should only respond with the JSON result in the format as described below:
1. Analyze the provided code and error: Examine the user's Python code to understand its functionality and structure. Combine the code with the error message, locate the error location, and analyze the specific reason for the error step by step.
2. Evaluate the feedback information: Review the user's feedback, including Current Working Directiory, Files And Folders in Current Working Directiory, combine with the previous analysis to further analyze the cause of the error.
@@ -269,13 +288,13 @@ prompt = {
And you should also follow the following criteria:
1. Ensure accurate understanding of the Python code and the error.
2. There are only two types of errors, External Supplementation Required Errors and Internal Code Modification Errors.
- 3. Understanding the definition of External Supplementation Required Errors: This type of error involves not only modifying the code itself, but also requiring some additional operations in the running environment of the code, this requires new tasks to complete the additional operations.
+ 3. Understanding the definition of External Supplementation Required Errors: This type of error involves not only modifying the code itself, but also requiring some additional operations in the running environments of the code, this requires new tasks to complete the additional operations.
4. Understanding the definition of Internal Code Modification Errors: This type of error can be resolved by modifying the code itself without having to perform any additional steps outside of the code.
5. Provide clear, logical reasoning.
6. The value of type can only be 'replan' or 'amend'.
7. In User's information, 'Working Directory' represents the root directory of the working directory, and 'Current Working Directory' represents the directory where the current task is located.
''',
- '_USER_ERROR_ANALYSIS_PROMPT' : '''
+ '_USER_ERROR_ANALYSIS_PROMPT': '''
User's information are as follows:
Current Code: {current_code}
Task: {task}
@@ -284,9 +303,9 @@ prompt = {
Working Directiory: {working_dir}
Files And Folders in Current Working Directiory: {files_and_folders}
''',
-
- # Tool usage prompt in os
- '_SYSTEM_TOOL_USAGE_PROMPT' : '''
+
+ # Tool usage prompts in os
+ '_SYSTEM_TOOL_USAGE_PROMPT': '''
You are a useful AI assistant capable of accessing APIs to complete user-specified tasks, according to API documentation,
by using the provided ToolRequestUtil tool. The API documentation is as follows:
{openapi_doc}
@@ -308,14 +327,14 @@ prompt = {
"""
Please begin your code completion:
''',
- '_USER_TOOL_USAGE_PROMPT' : '''
- from friday.core.tool_request_util import ToolRequestUtil
+ '_USER_TOOL_USAGE_PROMPT': '''
+ from oscopilot.tool_repository.manager.tool_request_util import ToolRequestUtil
tool_request_util = ToolRequestUtil()
# TODO: your code here
''',
- # QA prompt in os
- '_SYSTEM_QA_PROMPT' : '''
+ # QA prompts in os
+ '_SYSTEM_QA_PROMPT': '''
You are a helpful ai assistant that can answer the question with the help of the context provided by the user in a step by step manner. The full question may help you to solve the current question.
If you don't know how to answer the user's question, answer "I don't know." instead of making up an answer.
And you should also follow the following criteria:
@@ -323,36 +342,36 @@ prompt = {
2. If your current solution is incorrect but you have a potential solution, please implement your potential solution directly.
3. If you lack specific knowledge but can make inferences based on relevant knowledge, you can try to infer the answer to the question.
''',
- '_USER_QA_PROMPT' : '''
+ '_USER_QA_PROMPT': '''
Context: {context}
Full Question: {question}
Current Question: {current_question}
'''
-
+
},
- 'planning_prompt' : {
- # Task decompose prompt in os
- '_SYSTEM_TASK_DECOMPOSE_PROMPT' : '''
+ 'planning_prompt': {
+ # Task decompose prompts in os
+ '_SYSTEM_TASK_DECOMPOSE_PROMPT': '''
You are an expert in making plans.
I will give you a task and ask you to decompose this task into a series of subtasks. These subtasks can form a directed acyclic graph, and each subtask is an atomic operation. Through the execution of topological sorting of subtasks, I can complete the entire task.
You should only respond with a reasoning process and a JSON result in the format as described below:
- 1. Carry out step-by-step reasoning based on the given task until the task is completed. Each step of reasoning is decomposed into sub-tasks. For example, the current task is to reorganize the text files containing the word 'agent' in the folder called document into the folder called agent. Then the reasoning process is as follows: According to Current Working Directiory and Files And Folders in Current Working Directiory information, the folders documernt and agent exist, so firstly, retrieve the txt text in the folder call document in the working directory. If the text contains the word "agent", save the path of the text file into the list, and return. Secondly, put the retrieved files into a folder named agent based on the file path list obtained by executing the previous task.
- 2. There are three types of subtasks, the first is a task that requires the use of APIs to access internet resources to obtain information, such as retrieving information from the Internet, this type of task is called 'API subtask', and all available APIs are only listed in the API List. The second is a task that does not require the use of API tools but need to write code to complete, which is called 'Code subtask', 'Code subtask' usually only involves operating system or file operations. The third is called 'QA subtask', It neither requires writing code nor calling API to complete the task, it will analyze the current subtask description and the return results of the predecessor tasks to get an appropriate answer.
- 3. Each decomposed subtask has four attributes: name, task description, and dependencies. 'name' abstracts an appropriate name based on the reasoning process of the current subtask. 'description' is the process of the current subtask, and if the current task is related to a corresponding file operation, the path to the file needs to be written in the 'description'. 'dependencies' refers to the list of task names that the current task depends on based on the reasoning process. These tasks must be executed before the current task. 'type' indicates whether the current task is a Code task or a API task or a QA task, If it is a Code task, its value is 'Code', if it is a API task, its value is 'API', if it is a QA task, its value is 'QA'.
- 4. In JSON, each decomposed subtask contains four attributes: name, description, dependencies and type, which are obtained through reasoning about the task. The key of each subtask is the 'name' attribute of the subtask.
+ 1. Carry out step-by-step reasoning based on the given task until the task is completed. Each step of reasoning is decomposed into subtasks. For example, the current task is to reorganize the text files containing the word 'agents' in the folder called document into the folder called agents. Then the reasoning process is as follows: According to Current Working Directiory and Files And Folders in Current Working Directiory information, the folders documernt and agents exist, so firstly, retrieve the txt text in the folder call document in the working directory. If the text contains the word "agents", save the path of the text file into the list, and return. Secondly, put the retrieved files into a folder named agents based on the file path list obtained by executing the previous subtask.
+ 2. There are three types of subtasks, the first is a subtask that requires the use of APIs to access internet resources to obtain information, such as retrieving information from the Internet, this type of task is called 'API subtask', and all available APIs are only listed in the API List. The second is a subtask that does not require the use of API tools but need to write code to complete, which is called 'Code subtask', 'Code subtask' usually only involves operating system or file operations. The third is called 'QA subtask', it neither requires writing code nor calling API to complete the task, it will analyze the current subtask description and the return results of the predecessor subtasks to get an appropriate answer.
+ 3. Each decomposed subtask has four attributes: name, task description, and dependencies. 'name' abstracts an appropriate name based on the reasoning process of the current subtask. 'description' is the process of the current subtask, if the task includes a specific file path, then the 'description' in all subtasks must contain the path to that file. 'dependencies' refers to the list of subtask names that the current subtask depends on based on the reasoning process. These subtasks must be executed before the current subtask. 'type' indicates whether the current subtask is a Code subtask or a API subtask or a QA subtask, If it is a Code subtask, its value is 'Code', if it is a API subtask, its value is 'API', if it is a QA subtask, its value is 'QA'.
+ 4. In JSON, each decomposed subtask contains four attributes: name, description, dependencies and type, which are obtained through reasoning about the subtask. The key of each subtask is the 'name' attribute of the subtask.
5. Continuing with the example in 1, the format of the JSON data I want to get is as follows:
```json
{
"retrieve_files" : {
"name": "retrieve_files",
- "description": "retrieve the txt text in the folder call document in the working directory. If the text contains the word "agent", save the path of the text file into the list, and return.",
+ "description": "retrieve the txt text in the folder call document in the working directory. If the text contains the word "agents", save the path of the text file into the list, and return.",
"dependencies": [],
"type" : "Code"
},
"organize_files" : {
"name": "organize_files",
- "description": "put the retrieved files into a folder named agent based on the file path list obtained by executing the previous task.",
+ "description": "put the retrieved files into a folder named agents based on the file path list obtained by executing the previous task.",
"dependencies": ["retrieve_files"],
"type": "Code"
}
@@ -360,50 +379,51 @@ prompt = {
```
And you should also follow the following criteria:
1. A task can be decomposed down into one or more subtasks, depending on the complexity of the task.
- 2. The Action List I gave you contains the name of each action and the corresponding operation description. These actions are all atomic code task. You can refer to these atomic operations to decompose the code task.
+ 2. The Tool List I gave you contains the name of each tool and the corresponding operation description. These tools are all atomic code task. You can refer to these atomic operations to decompose the code subtask.
3. If it is a pure mathematical problem, you can write code to complete it, and then process a QA subtask to analyze the results of the code to solve the problem.
4. The decomposed subtasks can form a directed acyclic graph based on the dependencies between them.
5. The description information of the subtask must be detailed enough, no entity and operation information in the task can be ignored.
- 6. 'Current Working Directiory' and 'Files And Folders in Current Working Directiory' specify the path and directory of the current working directory. These information may help you understand and generate tasks.
+ 6. 'Current Working Directiory' and 'Files And Folders in Current Working Directiory' specify the path and directory of the current working directory. These information may help you understand and generate subtasks.
7. The tasks currently designed are compatible with and can be executed on the present version of the system.
- 8. The current task may need the return results of its predecessor tasks. For example, the current task is: Move the text files containing the word 'agent' from the folder named 'document' in the working directory to a folder named 'agent'. We can decompose this task into two subtasks, the first task is to retrieve text files containing the word 'agent' from the folder named 'document', and return their path list. The second task is to move the txt files of the corresponding path to the folder named 'agent' based on the path list returned by the previous task.
+ 8. The current subtask may need the return results of its predecessor subtasks. For example, the current subtask is: Move the text files containing the word 'agents' from the folder named 'document' in the working directory to a folder named 'agents'. We can decompose this task into two subtasks, the first subtask is to retrieve text files containing the word 'agents' from the folder named 'document', and return their path list. The second subtask is to move the txt files of the corresponding path to the folder named 'agents' based on the path list returned by the previous subtask.
9. If the current subtask needs to use the return result of the previous subtask, then this process should be written in the task description of the subtask.
- 10. Please note that the name of a Code subtask must be abstract. For instance, if the subtask is to search for the word "agent," then the subtask name should be "search_word," not "search_agent." As another example, if the subtask involves moving a file named "test," then the subtask name should be "move_file," not "move_test."
+ 10. Please note that the name of a Code subtask must be abstract. For instance, if the subtask is to search for the word "agents," then the subtask name should be "search_word," not "search_agent." As another example, if the subtask involves moving a file named "test," then the subtask name should be "move_file," not "move_test." Additionally, if a tool in the Tool list is described as an abstraction of the current subtask, then the name of the current subtask should directly reuse the name of that tool.
11. When generating the subtask description, you need to clearly specify whether the operation targets a single entity or multiple entities that meet certain criteria.
- 12. When decomposing subtasks, avoid including redundant information. For instance, if the task is to move txt files containing the word 'agent' from the folder named 'document' to a folder named 'XXX', one subtask should be to retrieve text files containing the word 'agent' from the folder named 'document', and return their path list. Then, the next subtask should be to move the txt files to the folder named 'XXX' based on the path list returned by the previous task, rather than moving the txt files that contain the word 'agent' to the folder named 'XXX' based on the path list returned by the previous task. The latter approach would result in redundant information in the subtasks.
- 13. User's information provided you with a API List that includes the API path and their corresponding descriptions. These APIs are designed for interacting with internet resources, like the Internet.
+ 12. When decomposing subtasks, avoid including redundant information. For instance, if the task is to move txt files containing the word 'agents' from the folder named 'document' to a folder named 'XXX', one subtask should be to retrieve text files containing the word 'agents' from the folder named 'document', and return their path list. Then, the next subtask should be to move the txt files to the folder named 'XXX' based on the path list returned by the previous subtask, rather than moving the txt files that contain the word 'agents' to the folder named 'XXX' based on the path list returned by the previous subtask. The latter approach would result in redundant information in the subtasks.
+ 13. User's information provided you with a API List that includes the API path and their corresponding descriptions. These APIs are designed for interacting with internet resources, such as bing search, web page information, etc.
14. When decomposing subtasks, you need to pay attention to whether the current subtask involves obtaining data from internet resources, such as finding cat pictures on the Internet, retrieving information on a certain web page, etc., then you need to select the relevant API from the API List.
- 15. If the current subtask is a API task, the description of the task must include the API path of the specified API to facilitate my extraction through the special format of the API path. For example, if an API task is to use the arxiv API to find XXX, then the description of the task should be: "Use the "/tools/arxiv' API to search for XXX".
+ 15. If the current subtask is a API subtask, the description of the subtask must include the API path of the specified API to facilitate my extraction through the special format of the API path. For example, if an API subtask is to use the bing search API to find XXX, then the description of the subtask should be: "Use the "/tools/bing/searchv2' API to search for XXX".
16. Please note that QA subtasks will not be generated continuously, that is, there will be no dependency between any two QA subtasks.
- 17. A QA subtask can perform comprehension analysis task, such as content conversion and format transformation, information summarization or analysis, answering academic questions, language translation, creative writing, logical reasoning based on existing information, and providing daily life advice and guidance, etc.
+ 17. A QA subtask can perform comprehension analysis task, such as content conversion, format transformation, information summarization or analysis, answering academic questions, language translation, creative writing, logical reasoning based on existing information, and providing daily life advice and guidance, etc.
18. If the task involves file or operating system operations, such as file reading and writing, downloading, moving, then decompose the Code subtask. If the task requires the use of APIs to access internet resources to obtain information, such as web page retrieval, obtaining web page text content, etc., then decompose the API subtask. QA subtasks usually use the results of reading files from the Code task and the content returned by the API task to help complete intermediate steps or give the final answer to the task.
19. If the task does not involve any file operations or Internet data acquisition, then only plan a QA subtask, and the 'description' of the QA subtask must be the full content of the original task.
- 20. If the task is to read and analyze the content of a PowerPoint presentation, it can be broken down into two sub-tasks. The first is a Code sub-task, which involves extracting the text content of the PowerPoint slides into a list. The second is a QA sub-task, which complete the task base on the text information extracted from each slide.
- 21. Once the task involves obtaining knowledge such as books, articles, character information, etc. you need to plan API tasks to obtain this knowledge from the Internet.
+ 20. If the task is to read and analyze the content of a PowerPoint presentation, it can be broken down into two subtasks. The first is a Code subtask, which involves extracting the text content of the PowerPoint slides into a list. The second is a QA subtask, which complete the task base on the text information extracted from each slide.
+ 21. Once the task involves obtaining knowledge such as books, articles, character information, etc. you need to plan API subtasks to obtain this knowledge from the Internet.
22. When decomposing an API subtask which uses the Bing Load Page API, you need to proceed to plan a QA subtask for analyzing and summarizing the information returned by that API subtask. For example, if the task is to find information about XXX, then your task will be broken down into three subtasks. The first API subtask is to use the Bing Search API to find relevant web page links. The second API subtask is to use the Bing Load Page API to obtain the information of the web pages found in the previous subtask. The final sub-task is a QA subtask, which is used to analyze the web page information returned by the previous sub-task and complete the task.
23. When the task involves retrieving a certain detailed content, then after decomposing the API subtask using Bing Search API, you also need to decompose an API subtask using Bing Load Page API, using for more detailed content.
- 24. If the attached file is a png or jpg file, the task must first be decomposed a API subtask, which uses image caption API to analyze image and solve problem. If it is necessary to obtain information from the Internet, then an API subtask should be decomposed. Otherwise, proceed with a QA subtask, which analyzes and completes task based on the return from API subtask.
+ 24. If the attached file is a png or jpg file, you should first decompose a API subtask, which uses image caption API to analyze image and solve problem. If it is necessary to obtain information from the Internet, then an API subtask should be decomposed. Otherwise, proceed with a QA subtask, which analyzes and completes task based on the return from API subtask.
25. Please note that all available APIs are only in the API List. You should not make up APIs that are not in the API List.
26. If the attached file is a mp3 file, you can only break out two subtasks! The task must first be decomposed a API subtask, which uses audio2text API to transcribe mp3 audio to text. Then proceed with a QA subtask, which analyzes and completes task based on the return from API subtask.
- 27. Since the analyse or the content of the file are in the return value of the first subtask, if the following subtask requires the content or the analyse, the first subtask needs to be added to the dependencies of that subtask.
- 28. If the task has a path to the file, then the subtask that operates the file must write the full path of the file in the task description, for example, add a new sheet, write calculation results into a certain column, etc.
+ 27. Since the analyse or the content of the file are in the return value of the first decomposed subtask(usually a Code subtask), if the following subtask requires the content or the analyse, you should add the first subtask to the dependencies of current subtask.
+ 28. If the task is to perform operations on a specific file., then all the subtasks must write the full path of the file in the task description, so as to locate the file when executing the subtasks.
+ 29. If a task has attributes such as Task, Input, Output, and Path, it's important to know that Task refers to the task that needs to be completed. Input and Output are the prompts for inputs and outputs while writing the code functions during the task execution phase. Path is the file path that needs to be operated on.
''',
- '_USER_TASK_DECOMPOSE_PROMPT' : '''
+ '_USER_TASK_DECOMPOSE_PROMPT': '''
User's information are as follows:
System Version: {system_version}
Task: {task}
- Action List: {action_list}
+ Tool List: {tool_list}
API List: {api_list}
Current Working Directiory: {working_dir}
Files And Folders in Current Working Directiory: {files_and_folders}
''',
- # Task replan prompt in os
- '_SYSTEM_TASK_REPLAN_PROMPT' : '''
+ # Task replan prompts in os
+ '_SYSTEM_TASK_REPLAN_PROMPT': '''
You are an expert at designing new tasks based on the results of your reasoning.
When I was executing the code of current task, an issue occurred that is not related to the code. The user information includes a reasoning process addressing this issue. Based on the results of this reasoning, please design a new task to resolve the problem.
You should only respond with a reasoning process and a JSON result in the format as described below:
- 1. Design new tasks based on the reasoning process of current task errors. For example, the inference process analyzed that the reason for the error was that there was no numpy package in the environment, causing it to fail to run. Then the reasoning process for designing a new task is: According to the reasoning process of error reporting, because there is no numpy package in the environment, we need to use the pip tool to install the numpy package.
+ 1. Design new tasks based on the reasoning process of current task errors. For example, the inference process analyzed that the reason for the error was that there was no numpy package in the environments, causing it to fail to run. Then the reasoning process for designing a new task is: According to the reasoning process of error reporting, because there is no numpy package in the environments, we need to use the pip tool to install the numpy package.
2. There are three types of subtasks, the first is a task that requires the use of APIs to access internet resources to obtain information, such as retrieving information from the Internet, this type of task is called 'API subtask', and the second is a task that does not require the use of API tools but need to write code to complete, which is called 'Code subtask', 'Code subtask' usually only involves operating system or file operations. The third is called 'QA subtask', It neither requires writing code nor calling API to complete the task, it will analyze the current subtask description and the return results of the predecessor tasks to get an appropriate answer.
3. Each decomposed subtask has four attributes: name, task description, and dependencies. 'name' abstracts an appropriate name based on the reasoning process of the current subtask. 'description' is the process of the current subtask. 'dependencies' refers to the list of task names that the current task depends on based on the reasoning process. These tasks must be executed before the current task. 'type' indicates whether the current task is a Code task or a API task or a QA task, If it is a Code task, its value is 'Code', if it is a API task, its value is 'API', if it is a QA task, its value is 'QA'.
4. Continuing with the example in 1, the format of the JSON data I want to get is as follows:
@@ -411,7 +431,7 @@ prompt = {
{
"install_package" : {
"name": "install_package",
- "description": "Use pip to install the numpy package that is missing in the environment.",
+ "description": "Use pip to install the numpy package that is missing in the environments.",
"dependencies": [],
"type" : "Code"
}
@@ -419,45 +439,98 @@ prompt = {
```
And you should also follow the following criteria:
1. The tasks you design based on the reasoning process are all atomic operations. You may need to design more than one task to meet the requirement that each task is an atomic operation.
- 2. The Action List I gave you contains the name of each action and the corresponding operation description. These actions are all atomic operations. You can refer to these atomic operations to design new task.
- 3. If an atomic operation in the Action List can be used as a new task, then the name of the decomposed sub-task should be directly adopted from the name of that atomic action.
+ 2. The Tool List I gave you contains the name of each tool and the corresponding operation description. These tools are all atomic operations. You can refer to these atomic operations to design new task.
+ 3. If an atomic operation in the Tool List can be used as a new task, then the name of the decomposed sub-task should be directly adopted from the name of that atomic tool.
4. The dependency relationship between the newly added task and the current task cannot form a loop.
5. The description information of the new task must be detailed enough, no entity and operation information in the task can be ignored.
6. 'Current Working Directiory' and 'Files And Folders in Current Working Directiory' specify the path and directory of the current working directory. These information may help you understand and generate tasks.
7. The tasks currently designed are compatible with and can be executed on the present version of the system.
- 8. Please note that the name of a task must be abstract. For instance, if the task is to search for the word "agent," then the task name should be "search_word," not "search_agent." As another example, if the task involves moving a file named "test," then the task name should be "move_file," not "move_test.
+ 8. Please note that the name of a task must be abstract. For instance, if the task is to search for the word "agents," then the task name should be "search_word," not "search_agent." As another example, if the task involves moving a file named "test," then the task name should be "move_file," not "move_test.
9. Please note that QA subtasks will not be generated continuously, that is, there will be no dependency between any two QA subtasks.
10. A QA subtask can perform comprehension analysis task, such as content conversion and format transformation, information summarization or analysis, answering academic questions, language translation, creative writing, logical reasoning based on existing information, and providing daily life advice and guidance, etc.
''',
- '_USER_TASK_REPLAN_PROMPT' : '''
+ '_USER_TASK_REPLAN_PROMPT': '''
User's information are as follows:
Current Task: {current_task}
Current Task Description: {current_task_description}
System Version: {system_version}
reasoning: {reasoning}
- Action List: {action_list}
+ Tool List: {tool_list}
Current Working Directiory: {working_dir}
Files And Folders in Current Working Directiory: {files_and_folders}
''',
},
- 'retrieve_prompt' : {
- # action code filter prompt
- '_SYSTEM_ACTION_CODE_FILTER_PROMPT' : '''
+ 'retrieve_prompt': {
+ # tool code filter prompts
+ '_SYSTEM_ACTION_CODE_FILTER_PROMPT': '''
You are an expert in analyzing python code.
- I will assign you a task and provide a dictionary of action names along with their corresponding codes. Based on the current task, please analyze the dictionary to determine if there is any action whose code can be used to complete the task. If such a code exists, return the action name that corresponds to the code you believe is best suited for completing the task. If no appropriate code exists, return an empty string.
+ I will assign you a task and provide a dictionary of tool names along with their corresponding codes. Based on the current task, please analyze the dictionary to determine if there is any tool whose code can be used to complete the task. If such a code exists, return the tool name that corresponds to the code you believe is best suited for completing the task. If no appropriate code exists, return an empty string.
You should only respond with the format as described below:
- 1. First, understand the requirements of the task. Next, read the code for each action, understanding their functions and methods. Examine the methods and attributes within the class, learning about their individual purposes and return values. Finally, by combining the task with the parameters of each action class's __call__ method, determine whether the content of the task can serve as an argument for the __call__ method, thereby arriving at an analysis result.
- 2. Based on the above analysis results, determine whether there is code corresponding to the action that can complete the current task. If so, return the action name corresponding to the code you think is the most appropriate. If not, return an empty string.
- 3. Output Format: The final output should include one part: the name of the selected action or empty string, which must be enclosed in tags.
+ 1. First, understand the requirements of the task. Next, read the code for each tool, understanding their functions and methods. Examine the methods and attributes within the class, learning about their individual purposes and return values. Finally, by combining the task with the parameters of each tool class's __call__ method, determine whether the content of the task can serve as an argument for the __call__ method, thereby arriving at an analysis result.
+ 2. Based on the above analysis results, determine whether there is code corresponding to the tool that can complete the current task. If so, return the tool name corresponding to the code you think is the most appropriate. If not, return an empty string.
+ 3. Output Format: The final output should include one part: the name of the selected tool or empty string, which must be enclosed in tags.
And you should also follow the following criteria:
- 1. There may be multiple codes that meet the needs of completing the task, but I only need you to return the action name corresponding to the most appropriate code.
- 2. If no code can complete the task, be sure to return an empty string, rather than a name of an action corresponding to a code that is nearly but not exactly suitable.
+ 1. There may be multiple codes that meet the needs of completing the task, but I only need you to return the tool name corresponding to the most appropriate code.
+ 2. If no code can complete the task, be sure to return an empty string, rather than a name of a tool corresponding to a code that is nearly but not exactly suitable.
''',
- '_USER_ACTION_CODE_FILTER_PROMPT' : '''
+ '_USER_ACTION_CODE_FILTER_PROMPT': '''
User's information are as follows:
- Action Code Pair: {action_code_pair}
+ Tool Code Pair: {tool_code_pair}
Task: {task_description}
- ''',
- }
+ ''',
+ },
+
+ 'self_learning_prompt' : {
+ # self learning prompt
+ '_SYSTEM_COURSE_DESIGN_PROMPT' : '''
+ You are an expert in designing a python course focused entirely on using a specific Python package to operate a particular software, each lesson in the course includes specific tasks for operating the software package, as well as prompts for program input and output. Students will write Python code based on the content of each lesson and the relevant prompts to complete tasks, thereby learning how to use specific package to operate software.
+ I will provide you with the name of the software you need to learn, the specific Python package required to operate it, and an example of course design. Additionally, there may be a provision of the software's demo file path and its contents. I want you to design a software learning course, aimed at mastering skills for performing specific software operations using specific python package. Please generate a progressively challenging course based on the information and criteria below.
+ Excel Course Design Example: To help you better design a course on related software, here I provide you with an example of a course design for learning to manipulate Excel files using openpyxl. Lesson 1, use openpyxl to read all the contents of sheet 'Sheet1' in demo.xlsx, the input is the path of file and the name of the sheet, the output is the contents of 'Sheet1' in 'demo.xlsx' as a list of rows, where each row contains the data from the respective row in the sheet, and demo.xlsx is located in 'working_dir/demo.xlsx'. Lesson 2, use the Python package 'openpyxl' to read all the contents of column 'Product' of sheet 'Sheet1' in demo.xlsx, the input is the path of file, sheet name and column name, the output is the contents of column 'Product' of 'Sheet1' in 'demo.xlsx' as a list, and demo.xlsx is located in 'working_dir/demo.xlsx'. Lesson 3, use openpyxl to insert a new sheet named 'new sheet' into demo.xlsx, the input is the path of file and the name of the new sheet, the output is None, and demo.xlsx is located in 'working_dir/demo.xlsx'. Lesson 3, use the Python package 'openpyxl' to copy the 'Product' column from 'Sheet1' to 'Sheet2' in demo.xlsx. input is the path of the file, sheet name1, sheet name2, column name, output is None, and demo.xlsx is located in 'working_dir/demo.xlsx'. Lesson 5, use the Python package 'openpyxl' to create a histogram that represents the data from the 'Product' and 'Sales' columns in the 'Sheet1' of demo.xlsx, the input is the path of the file, sheet name, column name1, colunm name2, the output is None, and demo.xlsx is located in 'working_dir/demo.xlsx'. lesson 6, use openpyxl to sum the values under the 'sales' column from the sheet 'Sheet1', the input is the path of the file ,sheet name and column name, the output is the sum of the 'sales' column, and demo.xlsx is located in 'working_dir/demo.xlsx'.
+ Note that only six lessons are listed here for demonstration purposes; you will need to design the course to include as many lessons as possible to comprehensively learn Python package manipulation in practice.
+ You should only respond with the format as described below:
+ 1. Output Format: The course designed consists of lessons, all lessons designed must be organised into a JSON data format, where key is the name of the lesson and value is a detailed description of the lesson.
+ 2. Course design: The design of the course must progress from easy to difficult, with the more complex and challenging lessons later in the course incorporating the objectives of the earlier lessons.
+ 3. lesson's name and description: The lesson's name is a summary of its current contents, and the description of the lesson have three or four parts: Task, Input, Output, File Path(If it exists). Task is a detailed description of the course content, Input is the prompt for the input of the program, Output is the prompt for the output of the program, and File Path is the path of the corresponding operating file.
+ 4. Continuing with the Excel Course Design Example, the format of the JSON data I want to get is as follows:
+ ```json
+ {
+ "read_specified_sheet" : "Task: Use the Python package 'openpyxl' to read all the contents of sheet 'Sheet1' in demo.xlsx. Input: The path of file, sheet name. Output: return the contents of 'Sheet1' in 'demo.xlsx' as a list of rows, where each row contains the data from the respective row in the sheet. File Path: working_dir/demo.xlsx",
+ "read_specified_sheet_column" : "Task: Use the Python package 'openpyxl' to read all the contents of column 'Product' of sheet 'Sheet1' in demo.xlsx. Input: The path of file, sheet name and column name. Output: return the contents of column 'Product' of 'Sheet1' in 'demo.xlsx' as a list. File Path: working_dir/demo.xlsx",
+ "insert_new_sheet" : "Task: Use the Python package 'openpyxl' to insert a new sheet named 'new sheet' into demo.xlsx. Input: The path of file and the name of the new sheet. Output: None. File Path: working_dir/demo.xlsx",
+ "copy_column_to_another_sheet" : "Task: Use the Python package 'openpyxl' to copy the 'Product' column from 'Sheet1' to 'Sheet2' in demo.xlsx. Input: The path of the file, sheet name1, sheet name2, column name. Output: None. File Path: working_dir/demo.xlsx",
+ "plot_histogram_from_sheet " : "Task: Use the Python package 'openpyxl' to create a histogram that represents the data from the 'Product' and 'Sales' columns in the 'Sheet1' of demo.xlsx. Input: The path of the file, sheet name, column name1, colunm name2. Output: None. File Path: working_dir/demo.xlsx",
+ "sum_column_values_in_sheet" : "Task: Use the Python package 'openpyxl' to sum the values under the 'Sales' column from the sheet 'Sheet1'. Input: The path of the file ,sheet name and column name. Output: The sum of the 'sales' column in 'Sheet1'. File Path: working_dir/demo.xlsx"
+ }
+ ```
+ And you should also follow the following criteria:
+ 1. My goal is to learn and master all the functionalities of this package for operating the software, enabling practical solutions to real-world problems. Therefore, the course design should encompass all features of the package as comprehensively as possible.
+ 2. Each lesson's description should include the path of the corresponding operating file, if such a file exists, to facilitate learning directly on that file.
+ 3. Your operation is executed under the specified System Version, so you need to be aware that the generated course can be executed under that OS environment.
+ 4. If the Demo File Path is empty, you will need to generate a appropriate course, based on your understanding of the provided software and the package.
+ 5. If Demo File Path is not empty, you must have an in-depth understanding and analysis of File Content and design a comprehensive and detailed course based on File Content.
+ 6. Please note, an output of 'None' means that when students are learning a lesson, the code they write does not need to return a value. They only need to write the code according to the lesson task and input prompts to perform operations on the file.
+ 7. To help students better learn the course and achieve the teaching objectives, the tasks in the lessons must be as detailed and unambiguous as possible.
+ 8. The code written by students during their course must be sufficiently versatile. Therefore, when designing the course, you should be able to transform the key information of tasks within the lesson into function parameters. Moreover, each parameter's content should be explicitly detailed in the Input and Output sections.
+ ''',
+ '_USER_COURSE_DESIGN_PROMPT' : '''
+ User's information are as follows:
+ Software Name: {software_name}
+ Python Package Name: {package_name}
+ Demo File Path: {demo_file_path}
+ File Content: {file_content}
+ System Version: {system_version}
+ ''',
+
+ },
+
+ 'text_extract_prompt' : '''
+ Please return all the contents of the file.
+ File Path: {file_path}
+ Tips:
+ 1. You need to be aware that the contents of some files may be stored in different places, for example, the contents of Excel may stored in different sheets and the contents of PPT may stored in different slides. For such files, I would like to return the contents of files in a dictionary format, organized by each sheet or slide, for easy retrieval and reading.
+ 2. You can only break down the task into one subtask. The subtask is for reading out all the contents of the file.
+ 3. If the file is a sheet file, I would like the output to be a dictionary, the key should be the name of each sheet, and the value should be a list of lists, where each inner list contains the contents of a row from that sheet.
+ '''
+
}
diff --git a/oscopilot/tool_repository/__init__.py b/oscopilot/tool_repository/__init__.py
new file mode 100644
index 0000000..f9e5846
--- /dev/null
+++ b/oscopilot/tool_repository/__init__.py
@@ -0,0 +1,2 @@
+from .manager import *
+from .basic_tools import *
\ No newline at end of file
diff --git a/friday/agent/__init__.py b/oscopilot/tool_repository/api_tools/__init__.py
similarity index 100%
rename from friday/agent/__init__.py
rename to oscopilot/tool_repository/api_tools/__init__.py
diff --git a/friday/api/__init__.py b/oscopilot/tool_repository/api_tools/audio2text/__init__.py
similarity index 100%
rename from friday/api/__init__.py
rename to oscopilot/tool_repository/api_tools/audio2text/__init__.py
diff --git a/friday/api/audio2text/audio2text.py b/oscopilot/tool_repository/api_tools/audio2text/audio2text.py
similarity index 68%
rename from friday/api/audio2text/audio2text.py
rename to oscopilot/tool_repository/api_tools/audio2text/audio2text.py
index d7951e6..42f3c0c 100644
--- a/friday/api/audio2text/audio2text.py
+++ b/oscopilot/tool_repository/api_tools/audio2text/audio2text.py
@@ -1,14 +1,10 @@
from openai import OpenAI
-import os
-
-os.environ["OPENAI_API_KEY"] = ""
-os.environ["OPENAI_ORGANIZATION"] = ""
class Audio2TextTool:
def __init__(self) -> None:
self.client = OpenAI()
def caption(self,audio_file):
- # 使用 OpenAI Whisper API 进行语音识别
+ # Perform voice recognition using the OpenAI Whisper API
response = self.client.audio.transcriptions.create(
model="whisper-1",
file=audio_file
diff --git a/friday/api/audio2text/audio2text_service.py b/oscopilot/tool_repository/api_tools/audio2text/audio2text_service.py
similarity index 80%
rename from friday/api/audio2text/audio2text_service.py
rename to oscopilot/tool_repository/api_tools/audio2text/audio2text_service.py
index 10e15cd..b2e5786 100644
--- a/friday/api/audio2text/audio2text_service.py
+++ b/oscopilot/tool_repository/api_tools/audio2text/audio2text_service.py
@@ -15,15 +15,15 @@ class AudioTextQueryItem(BaseModel):
-@router.post("/tools/audio2text")
+@router.post("/tools/audio2text", summary="A tool that converts audio to natural language text.")
async def audio2text(item: AudioTextQueryItem = Depends()):
try:
- # 创建一个临时文件来保存上传的音频
+ # Create a temporary file to save the uploaded audio.
with open(item.file.filename, "wb") as buffer:
shutil.copyfileobj(item.file.file, buffer)
with open(item.file.filename, "rb") as audio:
caption = whisper_api.caption(audio_file=audio)
- # 清理临时文件
+ # Clean up temporary files.
os.remove(item.file.filename)
return {"text": caption}
except RuntimeError as e:
diff --git a/friday/api/audio2text/test.mp3 b/oscopilot/tool_repository/api_tools/audio2text/test.mp3
similarity index 100%
rename from friday/api/audio2text/test.mp3
rename to oscopilot/tool_repository/api_tools/audio2text/test.mp3
diff --git a/friday/api/arxiv/__init__.py b/oscopilot/tool_repository/api_tools/bing/__init__.py
similarity index 100%
rename from friday/api/arxiv/__init__.py
rename to oscopilot/tool_repository/api_tools/bing/__init__.py
diff --git a/friday/api/bing/bing_api_v2.py b/oscopilot/tool_repository/api_tools/bing/bing_api_v2.py
similarity index 50%
rename from friday/api/bing/bing_api_v2.py
rename to oscopilot/tool_repository/api_tools/bing/bing_api_v2.py
index 334c75c..9172e71 100644
--- a/friday/api/bing/bing_api_v2.py
+++ b/oscopilot/tool_repository/api_tools/bing/bing_api_v2.py
@@ -9,20 +9,29 @@ from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains.summarize import load_summarize_chain
from langchain import OpenAI
-import os
-
-# Set bing search and OpenAPI Key
-os.environ["BING_SUBSCRIPTION_KEY"] = ""
-os.environ["BING_SEARCH_URL"] = "https://api.bing.microsoft.com/v7.0/search"
-os.environ["OPENAI_API_KEY"] = ""
-os.environ["OPENAI_ORGANIZATION"] = ""
SEARCH_RESULT_LIST_CHUNK_SIZE = 3
RESULT_TARGET_PAGE_PER_TEXT_COUNT = 500
class BingAPIV2:
+ """
+ A class for interacting with the Bing Search API and performing subsequent processing on web page data.
+
+ This class encapsulates the functionality to perform web searches using Bing's API, load web pages,
+ chunk and embed text for analysis, summarize web pages, and attend to loaded pages based on specific queries.
+
+ Attributes:
+ search_engine (BingSearchAPIWrapper): Configured instance for executing searches with Bing's API.
+ web_loader (WebPageLoader): Utility for loading web page content.
+ web_chunker (RecursiveCharacterTextSplitter): Utility for splitting text into manageable chunks.
+ web_sniptter_embed (OpenAIEmbeddings): Embedding model for text chunks.
+ web_summarizer (OpenAI): Model for summarizing web page content.
+ """
def __init__(self) -> None:
+ """
+ Initializes the BingAPIV2 with components for search, web page loading, and text processing.
+ """
self.search_engine = BingSearchAPIWrapper(search_kwargs={'mkt': 'en-us','safeSearch': 'moderate'})
self.web_loader = WebPageLoader()
self.web_chunker = RecursiveCharacterTextSplitter(chunk_size=4500, chunk_overlap=0)
@@ -32,7 +41,22 @@ class BingAPIV2:
)
def search(self, key_words: str,top_k: int = 5, max_retry: int = 3):
- # return search.results(query,top_k)
+ """
+ Searches for web pages using Bing's API based on provided keywords.
+
+ Attempts the search up to a specified number of retries upon failure.
+
+ Args:
+ key_words (str): The keywords to search for.
+ top_k (int, optional): The number of search results to return. Defaults to 5.
+ max_retry (int, optional): The maximum number of retry attempts. Defaults to 3.
+
+ Returns:
+ list: A list of search results.
+
+ Raises:
+ RuntimeError: If the search attempts fail after reaching the maximum number of retries.
+ """
for _ in range(max_retry):
try:
result = self.search_engine.results(key_words,top_k)
@@ -45,12 +69,30 @@ class BingAPIV2:
raise RuntimeError("Failed to access Bing Search API.")
def load_page(self, url: str) -> str:
+ """
+ Loads the content of a web page given its URL.
+
+ Args:
+ url (str): The URL of the web page to load.
+
+ Returns:
+ str: The content of the web page as a string.
+ """
page_data = self.web_loader.load_data(url)
page_content_str = ""
if(page_data["data"][0] != None and page_data["data"][0]["content"] != None):
page_content_str = page_data["data"][0]["content"]
return page_content_str
def summarize_loaded_page(self,page_str):
+ """
+ Summarizes the content of a loaded web page.
+
+ Args:
+ page_str (str): The content of the web page to summarize.
+
+ Returns:
+ str: The summarized content of the web page.
+ """
if page_str == "":
return ""
web_chunks = self.web_chunker.create_documents([page_str])
@@ -58,6 +100,16 @@ class BingAPIV2:
main_web_content = summarize_chain.run(web_chunks)
return main_web_content
def attended_loaded_page(self,page_str,query_str):
+ """
+ Identifies and aggregates content from a loaded web page that is most relevant to a given query.
+
+ Args:
+ page_str (str): The content of the web page.
+ query_str (str): The query string to identify relevant content.
+
+ Returns:
+ str: The aggregated content from the web page that is most relevant to the query.
+ """
if page_str == "":
return ""
web_chunks = self.web_chunker.create_documents([page_str])
diff --git a/friday/api/bing/bing_service.py b/oscopilot/tool_repository/api_tools/bing/bing_service.py
similarity index 60%
rename from friday/api/bing/bing_service.py
rename to oscopilot/tool_repository/api_tools/bing/bing_service.py
index 8a83e36..fb94549 100644
--- a/friday/api/bing/bing_service.py
+++ b/oscopilot/tool_repository/api_tools/bing/bing_service.py
@@ -1,15 +1,20 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel,Field
from typing import Optional
-from .bing_api import BingAPI
+# from .bing_api import BingAPI
from .bing_api_v2 import BingAPIV2
from .image_search_api import ImageSearchAPI
import tiktoken
-
-BING_API = "" # set bing API
+import os
+from dotenv import load_dotenv
-# 计算网页内容对gpt4来说的token数,如果token太多就用3.5做摘要或者用向量数据库检索最相关的片段
+load_dotenv(override=True)
+
+BING_API = os.getenv('BING_SUBSCRIPTION_KEY') # set bing API
+
+
+# Calculate the number of tokens in the webpage content for GPT-4. If there are too many tokens, use GPT-3.5 for summarization or search the vector database for the most relevant segment.
def num_tokens_from_string(string: str) -> int:
"""Returns the number of tokens in a text string."""
encoding = tiktoken.encoding_for_model('gpt-4-1106-preview')
@@ -18,7 +23,7 @@ def num_tokens_from_string(string: str) -> int:
router = APIRouter()
-bing_api = BingAPI(BING_API)
+# bing_api = BingAPI(BING_API)
bing_api_v2 = BingAPIV2()
image_search_api = ImageSearchAPI(BING_API)
@@ -53,7 +58,7 @@ class PageItemV2(BaseModel):
# raise HTTPException(status_code=500, detail=page_detail)
# return {"page_content": page_detail}
-@router.get("/tools/bing/image_search")
+@router.get("/tools/bing/image_search", summary="Searches for images related to the provided keywords using the Bing Image Search API. It allows specifying the number of images to return (top_k) and retries the search up to a specified number of times (max_retry) in case of failures. The search is performed with a moderate safe search filter and is intended for use within an environments that requires image search capabilities. The function returns a list of images, including their names, URLs, and thumbnail information. If the search fails after the maximum number of retries, it raises a runtime error.")
async def image_search(item: QueryItemV2):
try:
if item.top_k == None:
@@ -63,7 +68,7 @@ async def image_search(item: QueryItemV2):
raise HTTPException(status_code=500, detail=str(e))
return search_results
-@router.get("/tools/bing/searchv2")
+@router.get("/tools/bing/searchv2", summary="Execute Bing Search - returns top web snippets related to the query. Avoid using complex filters like 'site:'. For detailed page content, further use the web browser tool.")
async def bing_search_v2(item: QueryItemV2):
try:
if item.top_k == None:
@@ -73,7 +78,7 @@ async def bing_search_v2(item: QueryItemV2):
raise HTTPException(status_code=500, detail=str(e))
return search_results
-@router.get("/tools/bing/load_pagev2")
+@router.get("/tools/bing/load_pagev2", summary="Web browser tool for detailed content retrieval and specific information extraction from a target URL.In the case of Wikipedia, the number of tokens on such pages is often too large to load the entire page, so the 'query' parameter must be given to perform a similarity query to find the most relevant pieces of content. The 'query' parameter should be assigned with your task description to find the most relevant content of the web page.It is important that your 'query' must retain enough details about the task, such as time, location, quantity, and other information, to ensure that the results obtained are accurate enough.")
async def load_page_v2(item: PageItemV2):
result = {"page_content": ""}
try:
diff --git a/friday/api/bing/image_search_api.py b/oscopilot/tool_repository/api_tools/bing/image_search_api.py
similarity index 100%
rename from friday/api/bing/image_search_api.py
rename to oscopilot/tool_repository/api_tools/bing/image_search_api.py
diff --git a/friday/api/bing/web_loader.py b/oscopilot/tool_repository/api_tools/bing/web_loader.py
similarity index 100%
rename from friday/api/bing/web_loader.py
rename to oscopilot/tool_repository/api_tools/bing/web_loader.py
diff --git a/friday/api/bing/__init__.py b/oscopilot/tool_repository/api_tools/image_caption/__init__.py
similarity index 100%
rename from friday/api/bing/__init__.py
rename to oscopilot/tool_repository/api_tools/image_caption/__init__.py
diff --git a/friday/api/image_caption/birds.jpg b/oscopilot/tool_repository/api_tools/image_caption/birds.jpg
similarity index 100%
rename from friday/api/image_caption/birds.jpg
rename to oscopilot/tool_repository/api_tools/image_caption/birds.jpg
diff --git a/friday/api/image_caption/gpt4v_caption.py b/oscopilot/tool_repository/api_tools/image_caption/gpt4v_caption.py
similarity index 92%
rename from friday/api/image_caption/gpt4v_caption.py
rename to oscopilot/tool_repository/api_tools/image_caption/gpt4v_caption.py
index ccc2006..51c78fb 100644
--- a/friday/api/image_caption/gpt4v_caption.py
+++ b/oscopilot/tool_repository/api_tools/image_caption/gpt4v_caption.py
@@ -1,9 +1,4 @@
from openai import OpenAI
-import os
-
-
-os.environ["OPENAI_API_KEY"] = ""
-os.environ["OPENAI_ORGANIZATION"] = ""
class ImageCaptionTool:
def __init__(self) -> None:
diff --git a/friday/api/image_caption/image_caption_service.py b/oscopilot/tool_repository/api_tools/image_caption/image_caption_service.py
similarity index 77%
rename from friday/api/image_caption/image_caption_service.py
rename to oscopilot/tool_repository/api_tools/image_caption/image_caption_service.py
index 6ff741d..930f92b 100644
--- a/friday/api/image_caption/image_caption_service.py
+++ b/oscopilot/tool_repository/api_tools/image_caption/image_caption_service.py
@@ -17,7 +17,7 @@ image_caption_api = ImageCaptionTool()
async def caption_parameters(query: Optional[str] = Form("What's in this image?"),url: Optional[str] = Form(None),image_file: Optional[UploadFile] = File(None)):
return {"query":query,"url":url,"image_file":image_file}
-@router.post("/tools/image_caption")
+@router.post("/tools/image_caption", summary="When the task is to question and answer based on local picture, you have to use the Image Caption tool, who can directly analyze picture to answer question and complete task. For local images you want to understand, you need to only give the image_file without url. It is crucial to provide the 'query' parameter, and its value must be the full content of the task itself.")
async def image_search(item: dict = Depends(caption_parameters)):
try:
if(item["query"] == None):
diff --git a/friday/api/calculator/__init__.py b/oscopilot/tool_repository/api_tools/wolfram_alpha/__init__.py
similarity index 100%
rename from friday/api/calculator/__init__.py
rename to oscopilot/tool_repository/api_tools/wolfram_alpha/__init__.py
diff --git a/friday/api/wolfram_alpha/test.py b/oscopilot/tool_repository/api_tools/wolfram_alpha/test.py
similarity index 100%
rename from friday/api/wolfram_alpha/test.py
rename to oscopilot/tool_repository/api_tools/wolfram_alpha/test.py
diff --git a/friday/api/wolfram_alpha/wolfram_alpha.py b/oscopilot/tool_repository/api_tools/wolfram_alpha/wolfram_alpha.py
similarity index 78%
rename from friday/api/wolfram_alpha/wolfram_alpha.py
rename to oscopilot/tool_repository/api_tools/wolfram_alpha/wolfram_alpha.py
index 13078a3..fab8694 100644
--- a/friday/api/wolfram_alpha/wolfram_alpha.py
+++ b/oscopilot/tool_repository/api_tools/wolfram_alpha/wolfram_alpha.py
@@ -2,13 +2,20 @@ from fastapi import APIRouter
import wolframalpha
from pydantic import BaseModel
from typing import Optional
+import os
+from dotenv import load_dotenv
+
+
+load_dotenv(override=True)
+
+WOLFRAMALPHA_APP_ID = os.getenv('WOLFRAMALPHA_APP_ID')
class QueryItem(BaseModel):
query: str
router = APIRouter()
-app_id = "XRY28U-7PVE2LRH7H" # Replace with your app id
+app_id = WOLFRAMALPHA_APP_ID
client = wolframalpha.Client(app_id)
@router.post("/tools/wolframalpha")
diff --git a/oscopilot/tool_repository/basic_tools/__init__.py b/oscopilot/tool_repository/basic_tools/__init__.py
new file mode 100644
index 0000000..6dac1df
--- /dev/null
+++ b/oscopilot/tool_repository/basic_tools/__init__.py
@@ -0,0 +1 @@
+from .text_extractor import *
\ No newline at end of file
diff --git a/friday/atom_action/__init__.py b/oscopilot/tool_repository/basic_tools/atom_action/__init__.py
similarity index 100%
rename from friday/atom_action/__init__.py
rename to oscopilot/tool_repository/basic_tools/atom_action/__init__.py
diff --git a/friday/atom_action/operations/__init__.py b/oscopilot/tool_repository/basic_tools/atom_action/operations/__init__.py
similarity index 100%
rename from friday/atom_action/operations/__init__.py
rename to oscopilot/tool_repository/basic_tools/atom_action/operations/__init__.py
diff --git a/friday/atom_action/operations/coding.py b/oscopilot/tool_repository/basic_tools/atom_action/operations/coding.py
similarity index 100%
rename from friday/atom_action/operations/coding.py
rename to oscopilot/tool_repository/basic_tools/atom_action/operations/coding.py
diff --git a/friday/atom_action/operations/files.py b/oscopilot/tool_repository/basic_tools/atom_action/operations/files.py
similarity index 100%
rename from friday/atom_action/operations/files.py
rename to oscopilot/tool_repository/basic_tools/atom_action/operations/files.py
diff --git a/friday/atom_action/operations/media.py b/oscopilot/tool_repository/basic_tools/atom_action/operations/media.py
similarity index 89%
rename from friday/atom_action/operations/media.py
rename to oscopilot/tool_repository/basic_tools/atom_action/operations/media.py
index 4b2e3fc..31a69d3 100644
--- a/friday/atom_action/operations/media.py
+++ b/oscopilot/tool_repository/basic_tools/atom_action/operations/media.py
@@ -1,7 +1,7 @@
import sys
sys.dont_write_bytecode = True
-from friday.atom_action.src import *
+from oscopilot.tool_repository.basic_tools.atom_action.src import *
def view_document(file_path) -> None:
return evince(file_path)
diff --git a/friday/atom_action/operations/routine.py b/oscopilot/tool_repository/basic_tools/atom_action/operations/routine.py
similarity index 100%
rename from friday/atom_action/operations/routine.py
rename to oscopilot/tool_repository/basic_tools/atom_action/operations/routine.py
diff --git a/friday/atom_action/operations/system.py b/oscopilot/tool_repository/basic_tools/atom_action/operations/system.py
similarity index 100%
rename from friday/atom_action/operations/system.py
rename to oscopilot/tool_repository/basic_tools/atom_action/operations/system.py
diff --git a/friday/atom_action/query/__init__.py b/oscopilot/tool_repository/basic_tools/atom_action/query/__init__.py
similarity index 100%
rename from friday/atom_action/query/__init__.py
rename to oscopilot/tool_repository/basic_tools/atom_action/query/__init__.py
diff --git a/friday/atom_action/query/device.py b/oscopilot/tool_repository/basic_tools/atom_action/query/device.py
similarity index 100%
rename from friday/atom_action/query/device.py
rename to oscopilot/tool_repository/basic_tools/atom_action/query/device.py
diff --git a/friday/atom_action/query/files.py b/oscopilot/tool_repository/basic_tools/atom_action/query/files.py
similarity index 100%
rename from friday/atom_action/query/files.py
rename to oscopilot/tool_repository/basic_tools/atom_action/query/files.py
diff --git a/friday/atom_action/query/package.py b/oscopilot/tool_repository/basic_tools/atom_action/query/package.py
similarity index 100%
rename from friday/atom_action/query/package.py
rename to oscopilot/tool_repository/basic_tools/atom_action/query/package.py
diff --git a/friday/atom_action/src/__init__.py b/oscopilot/tool_repository/basic_tools/atom_action/src/__init__.py
similarity index 100%
rename from friday/atom_action/src/__init__.py
rename to oscopilot/tool_repository/basic_tools/atom_action/src/__init__.py
diff --git a/friday/atom_action/src/bash.py b/oscopilot/tool_repository/basic_tools/atom_action/src/bash.py
similarity index 100%
rename from friday/atom_action/src/bash.py
rename to oscopilot/tool_repository/basic_tools/atom_action/src/bash.py
diff --git a/friday/atom_action/src/commands.py b/oscopilot/tool_repository/basic_tools/atom_action/src/commands.py
similarity index 100%
rename from friday/atom_action/src/commands.py
rename to oscopilot/tool_repository/basic_tools/atom_action/src/commands.py
diff --git a/oscopilot/tool_repository/basic_tools/base_action.py b/oscopilot/tool_repository/basic_tools/base_action.py
new file mode 100644
index 0000000..d7e1a9b
--- /dev/null
+++ b/oscopilot/tool_repository/basic_tools/base_action.py
@@ -0,0 +1,108 @@
+from typing import Optional
+
+
+class BaseAction:
+ """
+ A base class for defining actions with optional attributes like description, name,
+ timeout, and action type.
+
+ This class serves as a template for defining various actions that can be executed
+ within a system. It encapsulates common properties that any action would require,
+ such as a name, description, execution timeout, and the type of action. The class
+ is designed to be extended by more specific action implementations.
+
+ Attributes:
+ _name (str): The name of the action. Defaults to the class name if not provided.
+ _description (Optional[str]): A brief description of what the action does.
+ _timeout (int): The maximum time in seconds the action is allowed to run. Defaults to 2.
+ action_type (str): The type of action, must be one of 'BASH', 'CODE', 'TOOL'. Defaults to 'BASH'.
+
+ Raises:
+ AssertionError: If the provided `action_type` is not among the expected types.
+ NotImplementedError: If the `__call__` method is not implemented by a subclass.
+
+ Methods:
+ __call__(*args, **kwargs): Abstract method, intended to be implemented by subclasses
+ to execute the action.
+ _python(*lines): Constructs a Python command string from the given lines of code.
+ _import(*packages): Generates an import statement for importing everything from the
+ specified jarvis package.
+ __str__(): Returns the same string representation as `__repr__`.
+
+ Note:
+ This class is abstract and is meant to be subclassed to provide concrete implementations
+ of actions. The `__call__` method must be overridden by subclasses to define the action's
+ behavior when invoked.
+ """
+
+ def __init__(self,
+ description: Optional[str] = None,
+ name: Optional[str] = None,
+ timeout: int = 2,
+ action_type: Optional[str] = 'BASH') -> None:
+ if name is None:
+ name = self.__class__.__name__
+ self._name = name
+ self._description = description
+ self._timeout = timeout
+ assert action_type in ['BASH', 'CODE', 'TOOL']
+ self.action_type = action_type
+
+ def __call__(self, *args, **kwargs):
+ raise NotImplementedError
+
+ def _python(self, *lines):
+ return f'python -Bc "{"; ".join(lines)}"'
+
+ def _import(self, *packages):
+ return f'from jarvis.{".".join(packages)} import *'
+
+ @property
+ def timeout(self):
+ """
+ The maximum time in seconds the action is allowed to run.
+
+ This read-only property returns the action's execution timeout value. It defines
+ how long an action can take before it should be considered as failed or timed out.
+
+ Returns:
+ int: The timeout for the action in seconds.
+ """
+ return self._timeout
+
+ @property
+ def name(self):
+ """
+ The name of the action.
+
+ This read-only property returns the name of the action. If not explicitly set
+ during initialization, it defaults to the name of the subclass implementing
+ the action.
+
+ Returns:
+ str: The name of the action.
+ """
+ return self._name
+
+ @property
+ def description(self):
+ """
+ A brief description of what the action does.
+
+ This read-only property provides a short description of the action's purpose or
+ functionality. It can be used to give users or developers a quick overview of
+ what the action is intended to perform.
+
+ Returns:
+ Optional[str]: The description of the action, or None if not set.
+ """
+ return self._description
+
+ def __repr__(self):
+ return f'{self.name}:{self.description}'
+
+ def __str__(self):
+ return self.__repr__()
+
+if __name__ == '__main__':
+ action = BaseAction()
\ No newline at end of file
diff --git a/oscopilot/tool_repository/basic_tools/get_os_version.py b/oscopilot/tool_repository/basic_tools/get_os_version.py
new file mode 100644
index 0000000..2b23861
--- /dev/null
+++ b/oscopilot/tool_repository/basic_tools/get_os_version.py
@@ -0,0 +1,61 @@
+import platform
+
+def get_os_version():
+ """
+ Determines the operating system version of the current system.
+
+ This function checks the operating system of the current environments and attempts
+ to return a human-readable version string. For macOS, it uses the `platform.mac_ver()`
+ method. For Linux, it attempts to read the version information from `/etc/os-release`.
+ If the system is not macOS or Linux, or if the Linux version cannot be determined, it
+ defaults to a generic version string or "Unknown Operating System".
+
+ Returns:
+ str: A string describing the operating system version, or "Unknown Operating System"
+ if the version cannot be determined.
+ """
+ system = platform.system()
+
+ if system == "Darwin":
+ # macOS
+ return 'macOS ' + platform.mac_ver()[0]
+ elif system == "Linux":
+ try:
+ with open("/etc/os-release") as f:
+ lines = f.readlines()
+ for line in lines:
+ if line.startswith("PRETTY_NAME"):
+ return line.split("=")[1].strip().strip('"')
+ except FileNotFoundError:
+ pass
+
+ return platform.version()
+ else:
+ return "Unknown Operating System"
+
+
+def check_os_version(s):
+ """
+ Checks if the operating system version string matches known supported versions.
+
+ This function examines a given operating system version string to determine if it
+ contains known substrings that indicate support (e.g., "mac", "Ubuntu", "CentOS").
+ If the version string does not match any of the known supported versions, it raises
+ a ValueError.
+
+ Args:
+ s (str): The operating system version string to check.
+
+ Raises:
+ ValueError: If the operating system version is not recognized as a known
+ supported version.
+ """
+ if "mac" in s or "Ubuntu" in s or "CentOS" in s:
+ print("perating System Version:", s)
+ else:
+ raise ValueError("Unknown Operating System")
+
+
+if __name__ == "__main__":
+ os_version = get_os_version()
+ print("Operating System Version:", os_version)
\ No newline at end of file
diff --git a/oscopilot/tool_repository/basic_tools/text_extractor.py b/oscopilot/tool_repository/basic_tools/text_extractor.py
new file mode 100644
index 0000000..6a52659
--- /dev/null
+++ b/oscopilot/tool_repository/basic_tools/text_extractor.py
@@ -0,0 +1,17 @@
+from oscopilot.prompts.friday_pt import prompt
+
+
+class TextExtractor:
+ def __init__(self, agent):
+ super().__init__()
+ self.agent = agent
+ self.prompt = prompt['text_extract_prompt']
+
+ def extract_file_content(self, file_path):
+ """
+ Extract the content of the file.
+ """
+ extract_task = self.prompt.format(file_path=file_path)
+ self.agent.run(extract_task)
+ file_content = list(self.agent.planner.tool_node.values())[-1].return_val
+ return file_content
\ No newline at end of file
diff --git a/friday/api/chemical/__init__.py b/oscopilot/tool_repository/generated_tools/__init__.py
similarity index 100%
rename from friday/api/chemical/__init__.py
rename to oscopilot/tool_repository/generated_tools/__init__.py
diff --git a/friday/action_lib/actions.json b/oscopilot/tool_repository/generated_tools/generated_tools.json
similarity index 100%
rename from friday/action_lib/actions.json
rename to oscopilot/tool_repository/generated_tools/generated_tools.json
diff --git a/friday/api/database/__init__.py b/oscopilot/tool_repository/generated_tools/tool_code/__init__.py
similarity index 100%
rename from friday/api/database/__init__.py
rename to oscopilot/tool_repository/generated_tools/tool_code/__init__.py
diff --git a/friday/api/gmail/__init__.py b/oscopilot/tool_repository/generated_tools/tool_description/__init__.py
similarity index 100%
rename from friday/api/gmail/__init__.py
rename to oscopilot/tool_repository/generated_tools/tool_description/__init__.py
diff --git a/friday/api/google_calendar/__init__.py b/oscopilot/tool_repository/generated_tools/vectordb/87376376-45f5-49cf-ad48-cceb714e4d7c/__init__.py
similarity index 100%
rename from friday/api/google_calendar/__init__.py
rename to oscopilot/tool_repository/generated_tools/vectordb/87376376-45f5-49cf-ad48-cceb714e4d7c/__init__.py
diff --git a/friday/api/markdown/__init__.py b/oscopilot/tool_repository/generated_tools/vectordb/__init__.py
similarity index 100%
rename from friday/api/markdown/__init__.py
rename to oscopilot/tool_repository/generated_tools/vectordb/__init__.py
diff --git a/friday/api/ppt/__init__.py b/oscopilot/tool_repository/generated_tools/vectordb/cbe4261b-c780-4530-994f-1f53067d9c31/__init__.py
similarity index 100%
rename from friday/api/ppt/__init__.py
rename to oscopilot/tool_repository/generated_tools/vectordb/cbe4261b-c780-4530-994f-1f53067d9c31/__init__.py
diff --git a/friday/action_lib/vectordb/chroma.sqlite3 b/oscopilot/tool_repository/generated_tools/vectordb/chroma.sqlite3
similarity index 93%
rename from friday/action_lib/vectordb/chroma.sqlite3
rename to oscopilot/tool_repository/generated_tools/vectordb/chroma.sqlite3
index 28da669..063b966 100644
Binary files a/friday/action_lib/vectordb/chroma.sqlite3 and b/oscopilot/tool_repository/generated_tools/vectordb/chroma.sqlite3 differ
diff --git a/oscopilot/tool_repository/manager/__init__.py b/oscopilot/tool_repository/manager/__init__.py
new file mode 100644
index 0000000..72403de
--- /dev/null
+++ b/oscopilot/tool_repository/manager/__init__.py
@@ -0,0 +1 @@
+from .tool_manager import *
\ No newline at end of file
diff --git a/oscopilot/tool_repository/manager/action_node.py b/oscopilot/tool_repository/manager/action_node.py
new file mode 100644
index 0000000..7d4847a
--- /dev/null
+++ b/oscopilot/tool_repository/manager/action_node.py
@@ -0,0 +1,114 @@
+class ActionNode:
+ """
+ Represents an action node in a workflow or execution graph, encapsulating details like the action's name, description,
+ return value, relevant code snippets, next actions, execution status, and action type.
+
+ Attributes:
+ _name (str): The name of the action.
+ _description (str): A brief description of what the action does.
+ _return_val (str): The value returned by the action upon execution.
+ _relevant_code (dict): A dictionary mapping relevant code snippets or references associated with the action.
+ _next_action (dict): A dictionary mapping subsequent actions that depend on the current action.
+ _status (bool): The execution status of the action, indicating whether it has been successfully executed.
+ _type (str): The type of the action, categorizing its purpose or method of execution.
+ """
+ def __init__(self, name, description, node_type):
+ """
+ Initializes an instance of the ActionNode class with the given attributes.
+
+ Args:
+ name (str): The name of the action.
+ description (str): A description of the action.
+ type (str): The type of the action.
+ """
+ self._name = name
+ self._description = description
+ self._return_val = ''
+ self._relevant_code = {}
+ self._next_action = {}
+ self._status = False
+ self._type = node_type
+
+ @property
+ def name(self):
+ """
+ Returns the name of the action.
+
+ Returns:
+ str: The action's name.
+ """
+ return self._name
+
+ @property
+ def description(self):
+ """
+ Returns the description of the action.
+
+ Returns:
+ str: The action's description.
+ """
+ return self._description
+
+ @property
+ def return_val(self):
+ """
+ Returns the return value of the action.
+
+ Returns:
+ str: The value returned by the action upon execution.
+ """
+ return self._return_val
+
+ @property
+ def relevant_action(self):
+ """
+ Returns the relevant code snippets or references associated with the action.
+
+ Returns:
+ dict: The action's relevant code snippets or references.
+ """
+ return self._relevant_code
+
+ @property
+ def status(self):
+ """
+ Returns the execution status of the action.
+
+ Returns:
+ bool: True if the action has been executed successfully, False otherwise.
+ """
+ return self._status
+
+ @property
+ def node_type(self):
+ """
+ Returns the type of the action.
+
+ Returns:
+ str: The action's type.
+ """
+ return self._type
+
+ @property
+ def next_action(self):
+ """
+ Returns subsequent actions that depend on the current action.
+
+ Returns:
+ dict: A mapping of subsequent actions.
+ """
+ return self._next_action
+
+ def __str__(self):
+ """
+ Provides a string representation of the ActionNode instance.
+
+ Returns:
+ str: A formatted string detailing the action's properties.
+ """
+ return f"name: {self.name} \n description: {self.description} \n return: {self.return_val} \n relevant_action: {self._relevant_code} \n next_action: {self.next_action} \n status: {self.status} \n type: {self.node_type}"
+
+
+if __name__ == '__main__':
+ node = ActionNode('temp','xxx')
+ print(node.name)
\ No newline at end of file
diff --git a/oscopilot/tool_repository/manager/api_server.py b/oscopilot/tool_repository/manager/api_server.py
new file mode 100644
index 0000000..43dab64
--- /dev/null
+++ b/oscopilot/tool_repository/manager/api_server.py
@@ -0,0 +1,54 @@
+import os
+
+from fastapi import FastAPI
+from oscopilot.utils.server_config import ConfigManager
+
+app = FastAPI()
+
+# Import your services
+from oscopilot.tool_repository.api_tools.bing.bing_service import router as bing_router
+from oscopilot.tool_repository.api_tools.audio2text.audio2text_service import router as audio2text_router
+from oscopilot.tool_repository.api_tools.image_caption.image_caption_service import router as image_caption_router
+from oscopilot.tool_repository.api_tools.wolfram_alpha.wolfram_alpha import router as wolfram_alpha_router
+
+from starlette.middleware.base import BaseHTTPMiddleware
+from starlette.requests import Request
+
+
+class LoggingMiddleware(BaseHTTPMiddleware):
+ async def dispatch(self, request: Request, call_next):
+ print(f"Incoming request: {request.method} {request.url}")
+ try:
+ response = await call_next(request)
+ except Exception as e:
+ print(f"Request error: {str(e)}")
+ raise e from None
+ else:
+ print(f"Outgoing response: {response.status_code}")
+ return response
+
+
+app.add_middleware(LoggingMiddleware)
+
+# Create a dictionary that maps service names to their routers
+services = {
+ "bing": bing_router, # bing_search, image_search and web_loader
+ "autio2text": audio2text_router,
+ "image_caption": image_caption_router,
+ "wolfram_alpha": wolfram_alpha_router
+}
+
+server_list = ["bing", "autio2text", "image_caption"]
+
+# Include only the routers for the services listed in server_list
+for service in server_list:
+ if service in services:
+ app.include_router(services[service])
+
+# proxy_manager = ConfigManager()
+# proxy_manager.apply_proxies()
+
+if __name__ == "__main__":
+ import uvicorn
+
+ uvicorn.run(app, host="0.0.0.0", port=8079)
diff --git a/oscopilot/tool_repository/manager/openapi.json b/oscopilot/tool_repository/manager/openapi.json
new file mode 100644
index 0000000..b55d54d
--- /dev/null
+++ b/oscopilot/tool_repository/manager/openapi.json
@@ -0,0 +1 @@
+{"openapi":"3.1.0","info":{"title":"FastAPI","version":"0.1.0"},"paths":{"/tools/bing/image_search":{"get":{"summary":"Searches for images related to the provided keywords using the Bing Image Search API. It allows specifying the number of images to return (top_k) and retries the search up to a specified number of times (max_retry) in case of failures. The search is performed with a moderate safe search filter and is intended for use within an environments that requires image search capabilities. The function returns a list of images, including their names, URLs, and thumbnail information. If the search fails after the maximum number of retries, it raises a runtime error.","operationId":"image_search_tools_bing_image_search_get","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryItemV2"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tools/bing/searchv2":{"get":{"summary":"Execute Bing Search - returns top web snippets related to the query. Avoid using complex filters like 'site:'. For detailed page content, further use the web browser tool.","operationId":"bing_search_v2_tools_bing_searchv2_get","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryItemV2"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/tools/bing/load_pagev2":{"get":{"summary":"Web browser tool for detailed content retrieval and specific information extraction from a target URL.In the case of Wikipedia, the number of tokens on such pages is often too large to load the entire page, so the 'query' parameter must be given to perform a similarity query to find the most relevant pieces of content. The 'query' parameter should be assigned with your task description to find the most relevant content of the web page.It is important that your 'query' must retain enough details about the task, such as time, location, quantity, and other information, to ensure that the results obtained are accurate enough.","operationId":"load_page_v2_tools_bing_load_pagev2_get","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageItemV2"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PageItemV2":{"properties":{"url":{"type":"string","title":"Url"},"query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query"}},"type":"object","required":["url"],"title":"PageItemV2"},"QueryItemV2":{"properties":{"query":{"type":"string","title":"Query"},"top_k":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Top K"}},"type":"object","required":["query"],"title":"QueryItemV2"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}
\ No newline at end of file
diff --git a/oscopilot/tool_repository/manager/tool_manager.py b/oscopilot/tool_repository/manager/tool_manager.py
new file mode 100644
index 0000000..8cbc381
--- /dev/null
+++ b/oscopilot/tool_repository/manager/tool_manager.py
@@ -0,0 +1,524 @@
+# __import__('pysqlite3')
+# import sys
+# sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
+
+from langchain.vectorstores import Chroma
+from langchain.embeddings.openai import OpenAIEmbeddings
+import argparse
+import json
+import sys
+import os
+import re
+from dotenv import load_dotenv
+load_dotenv(override=True)
+OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
+OPENAI_ORGANIZATION = os.getenv('OPENAI_ORGANIZATION')
+
+
+class ToolManager:
+ """
+ Manages tools within a repository, including adding, deleting, and retrieving tool information.
+
+ The `ToolManager` class provides a comprehensive interface for managing a collection
+ of tools, where each tool is associated with its code, description, and other metadata.
+ It supports operations such as adding new tools, checking for the existence of tools,
+ retrieving tool names, descriptions, and codes, and deleting tools from the collection.
+ It leverages a vector database for efficient retrieval of tools based on similarity searches.
+
+ Attributes:
+ generated_tools (dict): Stores the mapping relationship between tool names and their
+ information (code, description).
+ generated_tool_repo_dir (str): The directory path where the tools' information is stored,
+ including code files, description files, and a JSON file
+ containing the tools' metadata.
+ vectordb_path (str): The path to the vector database used for storing and retrieving
+ tool descriptions based on similarity.
+ vectordb (Chroma): An instance of the Chroma class for managing the vector database.
+
+ Note:
+ The class uses OpenAI's `text-embedding-ada-002` model by default for generating embeddings
+ via the `OpenAIEmbeddings` wrapper. Ensure that the `OPENAI_API_KEY` and `OPENAI_ORGANIZATION`
+ are correctly set for OpenAI API access.
+
+ This class is designed to facilitate the management of a dynamic collection of tools, providing
+ functionalities for easy addition, retrieval, and deletion of tools. It ensures that the tools'
+ information is synchronized across a local repository and a vector database for efficient
+ retrieval based on content similarity.
+ """
+
+ def __init__(self, generated_tool_repo_dir=None):
+ # generated_tools: Store the mapping relationship between descriptions and tools (associated through task names)
+ self.generated_tools = {}
+ self.generated_tool_repo_dir = generated_tool_repo_dir
+
+ with open(f"{self.generated_tool_repo_dir}/generated_tools.json") as f2:
+ self.generated_tools = json.load(f2)
+ self.vectordb_path = f"{generated_tool_repo_dir}/vectordb"
+
+ if not os.path.exists(self.vectordb_path):
+ os.makedirs(self.vectordb_path)
+ os.makedirs(f"{generated_tool_repo_dir}/tool_code", exist_ok=True)
+ os.makedirs(f"{generated_tool_repo_dir}/tool_description", exist_ok=True)
+ # Utilize the Chroma database and employ OpenAI Embeddings for vectorization (default: text-embedding-ada-002)
+ self.vectordb = Chroma(
+ collection_name="tool_vectordb",
+ embedding_function=OpenAIEmbeddings(
+ openai_api_key=OPENAI_API_KEY,
+ openai_organization=OPENAI_ORGANIZATION,
+ ),
+ persist_directory=self.vectordb_path,
+ )
+ assert self.vectordb._collection.count() == len(self.generated_tools), (
+ f"Tool Manager's vectordb is not synced with generated_tools.json.\n"
+ f"There are {self.vectordb._collection.count()} tools in vectordb but {len(self.generated_tools)} tools in generated_tools.json.\n"
+ )
+
+
+ @property
+ def programs(self):
+ """
+ Retrieve all the code from the code repository as a single string.
+
+ This property concatenates the code of all tools stored in the generated_tools
+ dictionary, separating each tool's code with two newlines.
+
+ Returns:
+ str: A string containing the code of all tools, each separated by two newlines.
+ """
+ programs = ""
+ for _, entry in self.generated_tools.items():
+ programs += f"{entry['code']}\n\n"
+ return programs
+
+
+ @property
+ def descriptions(self):
+ """
+ Retrieve the descriptions of all tools in a dictionary.
+
+ This property constructs a dictionary where each key is a tool name and its value
+ is the description of that tool, extracted from the generated_tools dictionary.
+
+ Returns:
+ dict: A dictionary mapping each tool name to its description.
+ """
+ descriptions = {}
+ for tool_name, entry in self.generated_tools.items():
+ descriptions.update({tool_name: entry["description"]})
+ return descriptions
+
+
+ @property
+ def tool_names(self):
+ """
+ Retrieve all tool class names from the generated tools.
+
+ This property provides access to the names of all tools stored in the
+ generated_tools dictionary, facilitating enumeration over tool names.
+
+ Returns:
+ KeysView[str]: A view of the dictionary's keys which are the names of the tools.
+ """
+ return self.generated_tools.keys()
+
+
+ def get_tool_code(self, tool_name):
+ """
+ Retrieve the code of a specific tool by its name.
+
+ Given a tool name, this method fetches and returns the code associated with
+ that tool from the generated_tools dictionary. If the tool does not exist,
+ a KeyError will be raised.
+
+ Args:
+ tool_name (str): The name of the tool for which the code is requested.
+
+ Returns:
+ str: The code of the specified tool.
+
+ Raises:
+ KeyError: If the tool_name does not exist in the generated_tools dictionary.
+ """
+ code = self.generated_tools[tool_name]['code']
+ return code
+
+
+ def add_new_tool(self, info):
+ """
+ Adds a new tool to the tool manager, including updating the vector database
+ and tool repository with the provided information.
+
+ This method processes the given tool information, which includes the task name,
+ code, and description. It prints out the task name and description, checks if
+ the tool already exists (rewriting it if so), and updates both the vector
+ database and the tool dictionary. Finally, it persists the new tool's code and
+ description in the repository and ensures the vector database is synchronized
+ with the generated tools.
+
+ Args:
+ info (dict): A dictionary containing the tool's information, which must
+ include 'task_name', 'code', and 'description'.
+
+ Raises:
+ AssertionError: If the vector database's count does not match the length
+ of the generated_tools dictionary after adding the new tool,
+ indicating a synchronization issue.
+ """
+ program_name = info["task_name"]
+ program_code = info["code"]
+ program_description = info["description"]
+ print(
+ f"\033[33m {program_name}:\n{program_description}\033[0m"
+ )
+ # If this task code already exists in the tool library, delete it and rewrite
+ if program_name in self.generated_tools:
+ print(f"\033[33mTool {program_name} already exists. Rewriting!\033[0m")
+ self.vectordb._collection.delete(ids=[program_name])
+ # Store the new task code in the vector database and the tool dictionary
+ self.vectordb.add_texts(
+ texts=[program_description],
+ ids=[program_name],
+ metadatas=[{"name": program_name}],
+ )
+ self.generated_tools[program_name] = {
+ "code": program_code,
+ "description": program_description,
+ }
+ assert self.vectordb._collection.count() == len(
+ self.generated_tools
+ ), "vectordb is not synced with generated_tools.json"
+ # Store the new task code and description in the tool repo, and enter the mapping relationship into the dictionary
+ with open(f"{self.generated_tool_repo_dir}/tool_code/{program_name}.py", "w") as fa:
+ fa.write(program_code)
+ with open(f"{self.generated_tool_repo_dir}/tool_description/{program_name}.txt", "w") as fb:
+ fb.write(program_description)
+ with open(f"{self.generated_tool_repo_dir}/generated_tools.json", "w") as fc:
+ json.dump(self.generated_tools,fc,indent=4)
+ self.vectordb.persist()
+ # with open(f"{self.generated_tool_repo_dir}/generated_tools.json") as f2:
+ # self.generated_tools = json.load(f2)
+
+
+ def exist_tool(self, tool):
+ """
+ Checks if a tool exists in the tool manager based on the tool name.
+
+ Args:
+ tool (str): The name of the tool to check.
+
+ Returns:
+ bool: True if the tool exists, False otherwise.
+ """
+ if tool in self.tool_names:
+ return True
+ return False
+
+
+ def retrieve_tool_name(self, query, k=10):
+ """
+ Retrieves related tool names based on a similarity search against a query.
+
+ This method performs a similarity search in the vector database for the given
+ query and retrieves the names of the top `k` most similar tools. It prints the
+ number of tools being retrieved and their names.
+
+ Args:
+ query (str): The query string to search for similar tools.
+ k (int, optional): The maximum number of similar tools to retrieve.
+ Defaults to 10.
+
+ Returns:
+ list[str]: A list of tool names that are most similar to the query,
+ up to `k` tools. Returns an empty list if no tools are found
+ or if `k` is 0.
+
+ """
+ k = min(self.vectordb._collection.count(), k)
+ if k == 0:
+ return []
+ print(f"\033[33mTool Manager retrieving for {k} Tools\033[0m")
+ # Retrieve descriptions of the top k related tasks.
+ docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k)
+ print(
+ f"\033[33mTool Manager retrieved tools: "
+ f"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}\033[0m"
+ )
+ tool_name = []
+ for doc, _ in docs_and_scores:
+ tool_name.append(doc.metadata["name"])
+ return tool_name
+
+
+ def retrieve_tool_description(self, tool_name):
+ """
+ Returns the descriptions of specified tools based on their names.
+
+ This method iterates over a list of tool names and retrieves the description
+ for each tool from the generated_tools dictionary. It compiles and returns
+ a list of these descriptions.
+
+ Args:
+ tool_name (list[str]): A list of tool names for which descriptions are requested.
+
+ Returns:
+ list[str]: A list containing the descriptions of the specified tools.
+ """
+ tool_description = []
+ for name in tool_name:
+ tool_description.append(self.generated_tools[name]["description"])
+ return tool_description
+
+
+ def retrieve_tool_code(self, tool_name):
+ """
+ Returns the code of specified tools based on their names.
+
+ Similar to retrieving tool descriptions, this method iterates over a list
+ of tool names and retrieves the code for each tool from the generated_tools
+ dictionary. It then compiles and returns a list of these codes.
+
+ Args:
+ tool_name (list[str]): A list of tool names for which code snippets are requested.
+
+ Returns:
+ list[str]: A list containing the code of the specified tools.
+ """
+ tool_code = []
+ for name in tool_name:
+ tool_code.append(self.generated_tools[name]["code"])
+ return tool_code
+
+
+ def delete_tool(self, tool):
+ """
+ Deletes all information related to a specified tool from the tool manager.
+
+ This method removes the tool's information from the vector database, the
+ generated_tools.json file, and also deletes the tool's code and description
+ files from the repository. It performs the deletion only if the tool exists
+ in the respective storage locations and provides console feedback for each
+ successful deletion action.
+
+ Args:
+ tool (str): The name of the tool to be deleted.
+
+ Note:
+ This method assumes that the tool's information is stored in a structured
+ manner within the tool manager's repository, including a separate code file
+ (.py), a description text file (.txt), and an arguments description text file
+ (.txt), all named after the tool.
+ """
+ if tool in self.generated_tools:
+ self.vectordb._collection.delete(ids=[tool])
+ print(
+ f"\033[33m delete {tool} from vectordb successfully! \033[0m"
+ )
+ # Delete the task from generated_tools.json
+ with open(f"{self.generated_tool_repo_dir}/generated_tools.json", "r") as file:
+ tool_infos = json.load(file)
+ if tool in tool_infos:
+ del tool_infos[tool]
+ with open(f"{self.generated_tool_repo_dir}/generated_tools.json", "w") as file:
+ json.dump(tool_infos, file, indent=4)
+ print(
+ f"\033[33m delete {tool} info from JSON successfully! \033[0m"
+ )
+ # del code
+ code_path = f"{self.generated_tool_repo_dir}/tool_code/{tool}.py"
+ if os.path.exists(code_path):
+ os.remove(code_path)
+ print(
+ f"\033[33m delete {tool} code successfully! \033[0m"
+ )
+ # del description
+ description_path = f"{self.generated_tool_repo_dir}/tool_description/{tool}.txt"
+ if os.path.exists(description_path):
+ os.remove(description_path)
+ print(
+ f"\033[33m delete {tool} description txt successfully! \033[0m"
+ )
+ # del args description
+ # args_path = f"{self.generated_tool_repo_dir}/args_description/{tool}.txt"
+ # if os.path.exists(args_path):
+ # os.remove(args_path)
+ # print(
+ # f"\033[33m delete {tool} args description txt successfully! \033[0m"
+ # )
+
+
+def print_error_and_exit(message):
+ """
+ Prints an error message to standard output and exits the program with a status code of 1.
+
+ This function is typically used to handle critical errors from which the program cannot
+ recover. It ensures that the error message is visible to the user before the program
+ terminates.
+
+ Args:
+ message (str): The error message to be printed.
+ """
+ print(f"Error: {message}")
+ sys.exit(1)
+
+
+def add_tool(toolManager, tool_name, tool_path):
+ """
+ Adds a new tool to the tool manager with the given name and code loaded from the specified path.
+
+ This function reads the tool's code from a file, extracts a description from the code using
+ a predefined pattern, and then adds the tool to the tool manager using the extracted information.
+ If the tool's description is not found within the code, the function will print an error message
+ and exit.
+
+ Args:
+ toolManager (ToolManager): The instance of ToolManager to which the tool will be added.
+ tool_name (str): The name of the tool to be added.
+ tool_path (str): The file system path to the source code of the tool.
+
+ Note:
+ The function expects the tool's code to contain a description defined as a string literal
+ assigned to `self._description` within the code. The description must be enclosed in double
+ quotes for it to be successfully extracted.
+ """
+ with open(tool_path, 'r') as file:
+ code = file.read()
+
+ pattern = r'self\._description = "(.*?)"'
+ match = re.search(pattern, code)
+ if match:
+ description = match.group(1)
+ # print(description)
+ # print(type(description))
+ info = {
+ "task_name" : tool_name,
+ "code" : code,
+ "description" : description
+ }
+ toolManager.add_new_tool(info)
+ print(f"Successfully add the tool: {tool_name} with path: {tool_path}")
+ else:
+ print_error_and_exit("No description found")
+
+
+def delete_tool(toolManager, tool_name):
+ """
+ Deletes a tool from the tool manager and prints a success message.
+
+ This function calls the `delete_tool` method of the given ToolManager instance
+ to remove the specified tool. Upon successful deletion, it prints a message
+ indicating the operation was successful.
+
+ Args:
+ toolManager (ToolManager): An instance of the ToolManager class.
+ tool_name (str): The name of the tool to be deleted.
+ """
+ toolManager.delete_tool(tool_name)
+ print(f"Successfully Delete the tool: {tool_name}")
+
+
+def get_open_api_doc_path():
+ """
+ Determines the file system path to the 'openapi.json' file located in the same directory as this script.
+
+ Returns:
+ str: The absolute path to the 'openapi.json' file.
+ """
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ open_api_path = os.path.join(script_dir, 'openapi.json')
+ return open_api_path
+
+
+def get_open_api_description_pair():
+ """
+ Extracts and returns a mapping of OpenAPI path names to their descriptions.
+
+ This function loads the OpenAPI specification from a 'openapi.json' file located
+ in the same directory as this script. It then iterates over the paths defined
+ in the OpenAPI specification, creating a dictionary that maps each path name
+ to its description (summary). If a path supports both 'get' and 'post' operations,
+ the description for the 'post' operation is preferred.
+
+ Returns:
+ dict: A dictionary mapping OpenAPI path names to their summary descriptions.
+ """
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ open_api_path = os.path.join(script_dir, 'openapi.json')
+ with open(open_api_path, 'r') as file:
+ open_api_json = json.load(file)
+ open_api_dict = open_api_json['paths']
+ open_api_description_pair = {}
+ for name, value in open_api_dict.items():
+ if 'post' in value:
+ open_api_description_pair[name] = value['post']['summary']
+ else:
+ open_api_description_pair[name] = value['get']['summary']
+ return open_api_description_pair
+
+
+def main():
+ """
+ The main entry point for managing generated tools for the FRIDAY project.
+
+ This function sets up a command-line interface for adding or deleting tools
+ within the FRIDAY project. It supports flags for adding a new tool, deleting
+ an existing tool, and specifies the name and path of the tool for the
+ respective operations. Based on the arguments provided, it initializes
+ a ToolManager instance and performs the requested add or delete operation.
+
+ The '--add' flag requires the '--tool_name' and '--tool_path' arguments to
+ specify the name and the path of the tool to be added. The '--delete' flag
+ requires only the '--tool_name' argument.
+
+ Usage:
+ python script.py --add --tool_name --tool_path
+ python script.py --delete --tool_name
+
+ Raises:
+ SystemExit: If no operation type is specified or required arguments are missing,
+ the program will print an error message and exit with a status code of 1.
+ """
+ parser = argparse.ArgumentParser(description='Manage generated tools for FRIDAY')
+
+ parser.add_argument('--generated_tool_repo_path', type=str, default='oscopilot/tool_repository/generated_tools', help='generated tool repo path')
+
+ parser.add_argument('--add', action='store_true',
+ help='Flag to add a new tool')
+ parser.add_argument('--delete', action='store_true',
+ help='Flag to delete a tool')
+ parser.add_argument('--tool_name', type=str,
+ help='Name of the tool to be added or deleted')
+ parser.add_argument('--tool_path', type=str,
+ help='Path of the tool to be added', required='--add' in sys.argv)
+
+ args = parser.parse_args()
+
+ toolManager = ToolManager(generated_tool_repo_dir=args.generated_tool_repo_path)
+
+ if args.add:
+ add_tool(toolManager, args.tool_name, args.tool_path)
+ elif args.delete:
+ delete_tool(toolManager, args.tool_name)
+ else:
+ print_error_and_exit("Please specify an operation type (add or del)")
+
+
+if __name__ == "__main__":
+ main()
+
+ # Retrieval
+ # res = toolManager.retrieve_tool_name("Open the specified text file in the specified folder using the default text viewer on Ubuntu.")
+ # print(res[0])
+
+ # Delete
+ # toolManager.delete_tool("zip_files")
+
+ # Add
+ # code = ''
+ # with open("temp.py", 'r') as file:
+ # code = file.read()
+ # info = {
+ # "task_name" : "XXX",
+ # "code" : code,
+ # "description" : "XXX"
+ # }
+ # toolManager.add_new_tool(info)
diff --git a/oscopilot/tool_repository/manager/tool_request_util.py b/oscopilot/tool_repository/manager/tool_request_util.py
new file mode 100644
index 0000000..033c837
--- /dev/null
+++ b/oscopilot/tool_repository/manager/tool_request_util.py
@@ -0,0 +1,71 @@
+import requests
+import os
+from dotenv import load_dotenv
+
+load_dotenv(override=True)
+API_BASE_URL = os.getenv('API_BASE_URL')
+
+class ToolRequestUtil:
+ """
+ A utility class for making HTTP requests to an API.
+
+ This class simplifies the process of sending HTTP requests using a persistent session
+ and predefined headers, including a User-Agent header to mimic a browser request. It's
+ designed to interact with APIs by sending GET or POST requests and handling file uploads.
+
+ Attributes:
+ session (requests.Session): A requests session for making HTTP requests.
+ headers (dict): Default headers to be sent with each request.
+ base_url (str): The base URL for the API endpoints.
+ """
+ def __init__(self):
+ """
+ Initializes the ToolRequestUtil with a session and default request headers.
+ """
+ self.session = requests.session()
+ self.headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML like Gecko) Chrome/52.0.2743.116 Safari/537.36'}
+ self.base_url = API_BASE_URL
+
+ def request(self, api_path, method, params=None, files=None, content_type="application/json"):
+ """
+ Sends a request to the specified API endpoint using the defined HTTP method.
+
+ This method constructs the request URL from the base URL and the API path. It supports
+ both GET and POST methods, including handling of JSON parameters, file uploads, and
+ different content types.
+
+ Args:
+ api_path (str): The path of the API endpoint.
+ method (str): The HTTP method to use for the request ('get' or 'post').
+ params (dict, optional): The parameters to include in the request. Defaults to None.
+ files (dict, optional): Files to be uploaded in a POST request. Defaults to None.
+ content_type (str, optional): The content type of the request, such as
+ 'application/json' or 'multipart/form-data'. Defaults to "application/json".
+
+ Returns:
+ dict: The JSON response from the API, or None if an error occurs.
+
+ Raises:
+ Prints an error message to the console if an HTTP request error occurs.
+ """
+ url = self.base_url + api_path
+ try:
+ if method.lower() == "get":
+ if content_type == "application/json":
+ result = self.session.get(url=url, json=params, headers=self.headers, timeout=60).json()
+ else:
+ result = self.session.get(url=url, params=params, headers=self.headers, timeout=60).json()
+ elif method.lower() == "post":
+ if content_type == "multipart/form-data":
+ result = self.session.post(url=url, files=files, data=params, headers=self.headers).json()
+ elif content_type == "application/json":
+ result = self.session.post(url=url, json=params, headers=self.headers).json()
+ else:
+ result = self.session.post(url=url, data=params, headers=self.headers).json()
+ else:
+ print("request method error!")
+ return None
+ return result
+ except Exception as e:
+ print("http request error: %s" % e)
+ return None
\ No newline at end of file
diff --git a/quick_start.py b/quick_start.py
new file mode 100644
index 0000000..7e66c17
--- /dev/null
+++ b/quick_start.py
@@ -0,0 +1,11 @@
+from oscopilot import FridayAgent
+from oscopilot import ToolManager
+from oscopilot import FridayExecutor, FridayPlanner, FridayRetriever
+from oscopilot.utils import setup_config, setup_pre_run
+
+args = setup_config()
+if not args.query:
+ args.query = "Create a new folder named 'test_friday'"
+task = setup_pre_run(args)
+agent = FridayAgent(FridayPlanner, FridayRetriever, FridayExecutor, ToolManager, config=args)
+agent.run(task=task)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 9e76dec..83fc5ed 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,22 +1,26 @@
aiohttp==3.9.1
aiosignal==1.3.1
+alabaster==0.7.16
annotated-types==0.6.0
anyio==3.7.1
asgiref==3.7.2
async-timeout==4.0.3
attrs==23.1.0
+Babel==2.14.0
backoff==2.2.1
bcrypt==4.1.2
beautifulsoup4==4.12.3
bs4==0.0.2
cachetools==5.3.2
certifi==2023.11.17
+cffi==1.16.0
charset-normalizer==3.3.2
chroma-hnswlib==0.7.3
chromadb==0.4.20
click==8.1.7
coloredlogs==15.0.1
contourpy==1.2.0
+cryptography==42.0.5
cycler==0.12.1
dataclasses-json==0.6.3
datasets==2.15.0
@@ -24,6 +28,7 @@ Deprecated==1.2.14
dill==0.3.7
distro==1.8.0
document==1.0
+docutils==0.20.1
et-xmlfile==1.1.0
exceptiongroup==1.2.0
fastapi==0.105.0
@@ -43,8 +48,11 @@ httpx==0.25.2
huggingface-hub==0.19.4
humanfriendly==10.0
idna==3.6
+imagesize==1.4.1
importlib-metadata==6.11.0
importlib-resources==6.1.1
+jaraco.context==4.3.0
+Jinja2==3.1.3
jsonpatch==1.33
jsonpointer==2.4
kiwisolver==1.4.5
@@ -54,14 +62,20 @@ langchain-community==0.0.1
langchain-core==0.0.13
langsmith==0.0.69
lxml==4.9.4
+markdown-it-py==3.0.0
+MarkupSafe==2.1.5
marshmallow==3.20.1
matplotlib==3.8.2
+mdit-py-plugins==0.4.0
+mdurl==0.1.2
mmh3==4.0.1
monotonic==1.6
+more-itertools==10.2.0
mpmath==1.3.0
multidict==6.0.4
multiprocess==0.70.15
mypy-extensions==1.0.0
+myst-parser==2.0.0
numpy==1.26.2
oauthlib==3.2.2
onnxruntime==1.16.3
@@ -80,6 +94,8 @@ opentelemetry-util-http==0.42b0
overrides==7.4.0
packaging==23.2
pandas==2.1.4
+pdfminer.six==20221105
+pdfplumber==0.10.4
pillow==10.2.0
posthog==3.1.0
protobuf==4.25.1
@@ -88,9 +104,12 @@ pyarrow==14.0.2
pyarrow-hotfix==0.6
pyasn1==0.5.1
pyasn1-modules==0.3.0
+pycparser==2.21
pydantic==2.5.2
pydantic_core==2.14.5
+Pygments==2.17.2
pyparsing==3.1.1
+pypdfium2==4.27.0
PyPika==0.48.9
pysqlite3==0.5.2
python-dateutil==2.8.2
@@ -106,7 +125,17 @@ rsa==4.9
seaborn==0.13.2
six==1.16.0
sniffio==1.3.0
+snowballstemmer==2.2.0
soupsieve==2.5
+Sphinx==7.2.6
+sphinx-rtd-theme==2.0.0
+sphinxcontrib-applehelp==1.0.8
+sphinxcontrib-devhelp==1.0.6
+sphinxcontrib-htmlhelp==2.0.5
+sphinxcontrib-jquery==4.1
+sphinxcontrib-jsmath==1.0.1
+sphinxcontrib-qthelp==1.0.7
+sphinxcontrib-serializinghtml==1.1.10
SQLAlchemy==2.0.23
starlette==0.27.0
sympy==1.12
@@ -124,8 +153,11 @@ uvloop==0.19.0
watchfiles==0.21.0
websocket-client==1.7.0
websockets==12.0
+wolframalpha==5.0.0
wrapt==1.16.0
XlsxWriter==3.1.9
+xmltodict==0.13.0
xxhash==3.4.1
yarl==1.9.4
zipp==3.17.0
+pytest==8.1.1
\ No newline at end of file
diff --git a/run.py b/run.py
deleted file mode 100644
index df7ba83..0000000
--- a/run.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import os
-import argparse
-import logging
-import json
-from datasets import load_dataset
-from friday.agent.friday_agent import FridayAgent
-import dotenv
-
-
-def random_string(length):
- import string
- import random
- characters = string.ascii_letters + string.digits
- random_string = ''.join(random.choice(characters) for _ in range(length))
- return random_string
-
-def main():
- parser = argparse.ArgumentParser(description='Inputs')
- parser.add_argument('--action_lib_path', type=str, default='friday/action_lib', help='tool repo path')
- parser.add_argument('--config_path', type=str, default='.env', help='openAI config file path')
- parser.add_argument('--query', type=str, help='Enter your task or simply press enter to execute the fallback task: "Move the text files containing the word \'agent\' from the folder named \'document\' to the path \'working_dir/agent\'"')
- parser.add_argument('--query_file_path', type=str, default='', help='Enter the path of the files for your task or leave empty if not applicable')
- parser.add_argument('--logging_filedir', type=str, default='log', help='log path')
- parser.add_argument('--logging_filename', type=str, default='temp.log', help='log file name')
- parser.add_argument('--logging_prefix', type=str, default=random_string(16), help='log file prefix')
- parser.add_argument('--score', type=int, default=8, help='critic score > score => store the tool')
- args = parser.parse_args()
-
- if args.query is None:
- args.query = "Move the text files containing the word 'agent' from the folder named 'document' to the path 'working_dir/agent'"
-
- if not os.path.exists(args.logging_filedir):
- os.mkdir(args.logging_filedir)
-
- logging.basicConfig(
- filename=os.path.join(args.logging_filedir, args.logging_filename),
- level=logging.INFO,
- format=f'[{args.logging_prefix}] %(asctime)s - %(levelname)s - %(message)s'
- )
-
- friday_agent = FridayAgent(config_path=args.config_path, action_lib_dir=args.action_lib_path)
- planning_agent = friday_agent.planner
- retrieve_agent = friday_agent.retriever
- execute_agent = friday_agent.executor
-
- task = 'Your task is: {0}'.format(args.query)
- if args.query_file_path != '':
- task = task + '\nThe path of the files you need to use: {0}'.format(args.query_file_path)
-
- print('Task:\n'+task)
- logging.info(task)
-
- # relevant action
- retrieve_action_name = retrieve_agent.retrieve_action_name(task)
- retrieve_action_description_pair = retrieve_agent.retrieve_action_description_pair(retrieve_action_name)
-
- # decompose task
- planning_agent.decompose_task(task, retrieve_action_description_pair)
-
- # iter each subtask
- while planning_agent.execute_list:
- action = planning_agent.execute_list[0]
- action_node = planning_agent.action_node[action]
- description = action_node.description
- logging.info("The current subtask is: {subtask}".format(subtask=description))
- code = ''
- # The return value of the current task
- result = ''
- next_action = action_node.next_action
- relevant_code = {}
- type = action_node.type
- pre_tasks_info = planning_agent.get_pre_tasks_info(action)
- if type == 'Code':
- # retrieve existing action
- retrieve_name = retrieve_agent.retrieve_action_name(description, 3)
- relevant_code = retrieve_agent.retrieve_action_code_pair(retrieve_name)
- # task execute step
- if type == 'QA':
- # result = execute_agent.question_and_answer_action(pre_tasks_info, task, task)
- if planning_agent.action_num == 1:
- result = execute_agent.question_and_answer_action(pre_tasks_info, task, task)
- else:
- result = execute_agent.question_and_answer_action(pre_tasks_info, task, description)
- print(result)
- logging.info(result)
- else:
- invoke = ''
- if type == 'API':
- api_path = execute_agent.extract_API_Path(description)
- code = execute_agent.api_action(description, api_path, pre_tasks_info)
- else:
- code, invoke = execute_agent.generate_action(action, description, pre_tasks_info, relevant_code)
- # Execute python tool class code
- state = execute_agent.execute_action(code, invoke, type)
- result = state.result
- logging.info(state)
- output = {
- "result": state.result,
- "error": state.error
- }
- logging.info(f"The subtask result is: {json.dumps(output)}")
- # Check whether the code runs correctly, if not, amend the code
- if type == 'Code':
- need_mend = False
- trial_times = 0
- critique = ''
- score = 0
- # If no error is reported, check whether the task is completed
- if state.error == None:
- critique, judge, score = execute_agent.judge_action(code, description, state, next_action)
- if not judge:
- print("critique: {}".format(critique))
- need_mend = True
- else:
- # Determine whether it is caused by an error outside the code
- reasoning, error_type = execute_agent.analysis_action(code, description, state)
- if error_type == 'replan':
- relevant_action_name = retrieve_agent.retrieve_action_name(reasoning)
- relevant_action_description_pair = retrieve_agent.retrieve_action_description_pair(relevant_action_name)
- planning_agent.replan_task(reasoning, action, relevant_action_description_pair)
- continue
- need_mend = True
- # The code failed to complete its task, fix the code
- while (trial_times < execute_agent.max_iter and need_mend == True):
- trial_times += 1
- print("current amend times: {}".format(trial_times))
- new_code, invoke = execute_agent.amend_action(code, description, state, critique, pre_tasks_info)
- critique = ''
- code = new_code
- # Run the current code and check for errors
- state = execute_agent.execute_action(code, invoke, type)
- result = state.result
- logging.info(state)
- # print(state)
- # Recheck
- if state.error == None:
- critique, judge, score = execute_agent.judge_action(code, description, state, next_action)
- # The task execution is completed and the loop exits
- if judge:
- need_mend = False
- break
- # print("critique: {}".format(critique))
- else: # The code still needs to be corrected
- need_mend = True
-
- # If the task still cannot be completed, an error message will be reported.
- if need_mend == True:
- print("I can't Do this Task!!")
- break
- else: # The task is completed, if code is save the code, args_description, action_description in lib
- if score >= args.score:
- execute_agent.store_action(action, code)
- print("Current task execution completed!!!")
- planning_agent.update_action(action, result, relevant_code, True, type)
- planning_agent.execute_list.remove(action)
-if __name__ == '__main__':
- dotenv.load_dotenv()
- main()
-
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..e3eff44
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,36 @@
+from setuptools import setup, find_packages
+
+
+with open('requirements.txt') as f:
+ requirements = f.read().splitlines()
+
+setup(
+ name="os-copilot",
+ version="0.1.0",
+ author="Zhiyong Wu and Chengcheng Han and Zichen Ding and Zhenmin Weng and Zhoumianze Liu and Shunyu Yao and Tao Yu and Lingpeng Kong",
+ author_email="wuzhiyong@pjlab.org.cn, hccngu@163.com",
+ description="An self-improving embodied conversational agents seamlessly integrated into the operating system to automate our daily tasks.",
+ long_description=open("README.md").read(),
+ long_description_content_type="text/markdown",
+ url="https://github.com/OS-Copilot/OS-Copilot",
+ license="MIT",
+
+ packages=find_packages(exclude=("docs", "temp", "pic", "log")),
+
+ install_requires=requirements,
+
+ entry_points={
+ "console_scripts": [
+ "friday=quick_start:main",
+ ],
+ },
+
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ ],
+ keywords="AI, LLMs, Large Language Models, Agent, OS, Operating System",
+
+ python_requires='>=3.10',
+)
\ No newline at end of file
diff --git a/test/test_data_loader.py b/test/test_data_loader.py
new file mode 100644
index 0000000..7713c16
--- /dev/null
+++ b/test/test_data_loader.py
@@ -0,0 +1,21 @@
+import pytest
+from oscopilot.utils import SheetTaskLoader, get_project_root_path
+
+class TestSheetTaskLoader:
+ def setup_method(self, method):
+ sheet_task_path = get_project_root_path() + "examples/SheetCopilot/sheet_task.jsonl"
+ self.sheet_task_loader = SheetTaskLoader(sheet_task_path)
+
+ def test_task2query(self):
+ assert self.sheet_task_loader.task2query("context.", "instructions.", "file_path") != ""
+
+
+ def load_sheet_task_dataset(self):
+ assert self.sheet_task_loader.load_sheet_task_dataset() != []
+
+ def test_get_task_by_id(self):
+ assert self.sheet_task_loader.get_data_by_task_id(1) != {}
+
+if __name__ == '__main__':
+ pytest.main()
+
diff --git a/test/test_self_learning.py b/test/test_self_learning.py
new file mode 100644
index 0000000..c47a030
--- /dev/null
+++ b/test/test_self_learning.py
@@ -0,0 +1,61 @@
+import pytest
+from oscopilot import FridayAgent, FridayExecutor, FridayPlanner, FridayRetriever, SelfLearner, SelfLearning, ToolManager, TextExtractor
+from oscopilot.utils import setup_config
+
+class TestSelfLearning:
+ def setup_method(self, method):
+ self.args = setup_config()
+ self.software_name = self.args.software_name
+ self.package_name = self.args.package_name
+ self.demo_file_path = self.args.demo_file_path
+ self.friday_agent = FridayAgent(FridayPlanner, FridayRetriever, FridayExecutor, ToolManager, config=self.args)
+ self.self_learning = SelfLearning(self.friday_agent, SelfLearner, ToolManager, self.args, TextExtractor)
+
+ def test_text_extract(self):
+ text_extractor = self.self_learning.text_extractor
+ file_content = text_extractor.extract_file_content(self.demo_file_path)
+ assert file_content != ""
+
+ def test_course_design(self):
+ file_content = """
+ Invoice No. Date Sales Rep Product Price Units Sales
+ 10500 2011-05-25 Joe Majestic 30 25 750
+ 10501 2011-05-25 Moe Majestic 30 9 270
+ 10501 2011-05-25 Moe Quad 32 21 672
+ 10501 2011-05-25 Moe Alpine 22 7 154
+ 10501 2011-05-25 Moe Carlota 25 11 275
+ 10502 2011-05-27 Moe Majestic 30 5 150
+ 10502 2011-05-27 Moe Carlota 25 25 625
+ 10503 2011-05-28 Chin Carlota 25 21 525
+ 10503 2011-05-28 Chin Alpine 22 16 352
+ 10503 2011-05-28 Chin Quad 32 4 128
+ 10503 2011-05-28 Chin Majestic 30 18 540
+ 10504 2011-05-28 Moe Bellen 23 17 391
+ 10504 2011-05-28 Moe Quad 32 8 256
+ 10505 2011-05-28 Joe Bellen 23 21 483
+ 10505 2011-05-28 Joe Carlota 25 8 200
+ 10505 2011-05-28 Joe Quad 32 17 544
+ 10505 2011-05-28 Joe Majestic 30 12 360
+ 10505 2011-05-28 Joe Alpine 22 18 396
+ """
+ course = self.self_learning.learner.design_course(self.software_name, self.package_name, self.demo_file_path, file_content)
+ assert course != {}
+
+ def test_learn_course(self):
+ # This is a demo course template, for testing you need to use the real course generated by the course_design method.
+ course = {
+ "read_contents_of_Sheet1": "Task: Use openpyxl to read all the contents of sheet 'Sheet1' in Invoices.xlsx. Input: The path of file and the sheet name. Output: All the content of sheet 'Sheet1' in Invoices.xlsx. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "read_contents_of_Sheet2": "Task: Use openpyxl to read all the contents of sheet 'Sheet2' in Invoices.xlsx. Input: The path of file and the sheet name. Output: All the content of sheet 'Sheet2' in Invoices.xlsx. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "insert_new_sheet": "Task: Use openpyxl to insert a new sheet named 'Summary' into Invoices.xlsx. Input: The path of file and the name of the new sheet. Output: None. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "calculate_total_sales": "Task: Use openpyxl to calculate the total sales from the 'Sales' column in sheet 'Sheet1'. Input: The path of the file, sheet name, and column name. Output: The total sales amount. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "update_price_with_discount": "Task: Use openpyxl to apply a 10% discount to all 'Price' values in sheet 'Sheet1' and update the sheet accordingly. Input: The path of the file, sheet name, and discount percentage. Output: None, but the 'Price' column in 'Sheet1' should be updated with discounted prices. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "calculate_acceleration_formula": "Task: Use openpyxl to calculate the acceleration for each 'Hanging Mass (m2) (kg)' in sheet 'Sheet2' using the formula provided in the sheet and update the 'Acceleration (m/s^2)' column. Input: The path of the file and sheet name. Output: None, but the 'Acceleration (m/s^2)' column in 'Sheet2' should be updated with calculated values. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "generate_sales_report": "Task: Use openpyxl to create a new sheet named 'Sales Report' that summarizes the total sales per 'Sales Rep' from sheet 'Sheet1'. Input: The path of the file and the name of the new sheet. Output: None, but a new sheet 'Sales Report' should be created with each 'Sales Rep' and their corresponding total sales. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx",
+ "plot_sales_histogram": "Task: Use openpyxl to plot a histogram for the sales data in sheet 'Sheet1' of Invoices.xlsx. Input: The path of the file and sheet name. Output: None, but a histogram plot should be generated and saved within the workbook. File Path: /home/heroding/桌面/FRIDAY_self_learning/jarvis/working_dir/Invoices.xlsx"
+ }
+ self.self_learning.learn_course(course)
+
+
+
+if __name__ == "__main__":
+ pytest.main()
diff --git a/working_dir/Dragging.xlsx b/working_dir/Dragging.xlsx
new file mode 100644
index 0000000..73a2228
Binary files /dev/null and b/working_dir/Dragging.xlsx differ
diff --git a/working_dir/Invoices.xlsx b/working_dir/Invoices.xlsx
new file mode 100644
index 0000000..3142785
Binary files /dev/null and b/working_dir/Invoices.xlsx differ