├── gpt_computer_agent ├── audio │ ├── __init__.py │ ├── input_box.py │ ├── stt_providers │ │ ├── openai.py │ │ └── openai_whisper_local.py │ ├── tts_providers │ │ ├── openai.py │ │ └── microsoft_local.py │ ├── wake_word.py │ ├── stt.py │ ├── tts.py │ └── record.py ├── gui │ ├── __init__.py │ ├── signal.py │ └── button.py ├── screen │ ├── __init__.py │ └── shot.py ├── tracing.py ├── utils │ ├── media │ │ ├── Down.png │ │ ├── Up.png │ │ ├── icon.ico │ │ ├── Audio.png │ │ ├── icon.icns │ │ ├── gca_logo.png │ │ ├── icon_16.png │ │ ├── icon_24.png │ │ ├── icon_256.png │ │ ├── icon_32.png │ │ ├── icon_48.png │ │ ├── Microphone.png │ │ ├── Screenshot.png │ │ ├── icon_48_active.png │ │ └── SF-Pro-Text-Bold.otf │ ├── folder.py │ ├── kot_db.py │ ├── function.py │ ├── pypi.py │ ├── user_id.py │ ├── telemetry.py │ ├── train.py │ └── chat_history.py ├── agent │ ├── __init__.py │ ├── background.py │ ├── chat_history.py │ ├── agent_tools.py │ ├── agent.py │ └── assistant.py ├── version.py ├── custom_callback.py ├── tooler.py ├── top_bar_wrapper.py ├── character.py ├── agentic.py ├── cu │ ├── run.py │ ├── base.py │ ├── ask_anthropic.py │ └── computer.py ├── llm.py ├── llm_settings.py ├── start.py ├── display_tools.py ├── standard_tools.py ├── __init__.py ├── mcp │ ├── mcp_langchain.ipynb │ └── tool.py └── teams.py ├── CONTRIBUTING.md ├── run.py ├── project.toml ├── MANIFEST.in ├── .github ├── close-label.yml ├── ISSUE_TEMPLATE │ ├── share-feedback.md │ ├── showcase-addition.yml │ ├── feature-request.yml │ └── question.yml ├── dependabot.yml ├── issue-report-config.json ├── workflows │ ├── add-to-project.yml │ ├── refactor.yml │ ├── add-comment-from-tag.yml │ ├── auto-rebase-pr.ym │ ├── release.yml │ ├── requirement_controller.yml │ ├── deploys.yml │ └── release_generation.yml ├── pull_request_template.md ├── issue-auto-comments.yml ├── pr-badge.ym └── CODE_OF_CONDUCT.md ├── .deepsource.toml ├── gca_setup_generator.py ├── refactor.py ├── LICENSE ├── requirements.txt ├── requirements.in ├── example_use_cases └── workday_summerizer.md ├── gca_setup.py ├── setup.py ├── .gitignore ├── bump.py ├── README.zh_CN.md └── README.zh_TW.md /gpt_computer_agent/audio/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gpt_computer_agent/gui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gpt_computer_agent/screen/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gpt_computer_agent/tracing.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We are open to any contribution as well. 2 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/input_box.py: -------------------------------------------------------------------------------- 1 | the_input_box_pre = "" -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from gpt_computer_agent import start 2 | 3 | 4 | start() 5 | -------------------------------------------------------------------------------- /project.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | 4 | include = '\.pyi?$' -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include gpt_computer_agent/utils/media/* 2 | include requirements.txt -------------------------------------------------------------------------------- /.github/close-label.yml: -------------------------------------------------------------------------------- 1 | 🐛 Bug: ✅ Fixed 2 | 🦄 Feature Request: ✅ Implemented 3 | 🤷‍♂️ Question: ✅ Answered 4 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | 6 | [analyzers.meta] 7 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/Down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/Down.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/Up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/Up.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon.ico -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/Audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/Audio.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon.icns -------------------------------------------------------------------------------- /gpt_computer_agent/agent/__init__.py: -------------------------------------------------------------------------------- 1 | from .agent import * 2 | from .assistant import * 3 | from .background import * 4 | from .chat_history import * 5 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/gca_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/gca_logo.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon_16.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon_24.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon_256.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon_32.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon_48.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/Microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/Microphone.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/Screenshot.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/icon_48_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/icon_48_active.png -------------------------------------------------------------------------------- /gpt_computer_agent/utils/media/SF-Pro-Text-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khulnasoft/gpt-computer-agent/HEAD/gpt_computer_agent/utils/media/SF-Pro-Text-Bold.otf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/share-feedback.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Share Feedback \U0001F308" 3 | about: Share what you think about GPT Comouter Agent, and any ideas or suggestions you have 4 | title: "[FEEDBACK]" 5 | labels: "\U0001F308 Feedback" 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/folder.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | currently_dir = os.path.dirname(os.path.abspath(__file__)) 4 | artifacts_dir = os.path.join(currently_dir, "artifacts") 5 | media_dir = os.path.join(currently_dir, "media") 6 | 7 | if not os.path.exists(artifacts_dir): 8 | os.makedirs(artifacts_dir) -------------------------------------------------------------------------------- /gpt_computer_agent/audio/stt_providers/openai.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ...llm import * 3 | except ImportError: 4 | from llm import * 5 | 6 | 7 | def stt_openai(audio_file): 8 | transcription = get_client().audio.transcriptions.create( 9 | model="whisper-1", file=audio_file 10 | ) 11 | return transcription.text 12 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/kot_db.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .folder import currently_dir, artifacts_dir, media_dir 3 | except: 4 | from folder import currently_dir, artifacts_dir, media_dir 5 | 6 | 7 | from kot import KOT 8 | 9 | 10 | 11 | kot_db_ = KOT("gca", folder=artifacts_dir, enable_hashing=True) 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/tts_providers/openai.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ...llm import * 3 | except ImportError: 4 | from llm import * 5 | 6 | 7 | def tts_openai(voice, text_chunk, location): 8 | response = get_client().audio.speech.create( 9 | model="tts-1", 10 | voice=voice, 11 | input=text_chunk, 12 | ) 13 | response.stream_to_file(location) 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | commit-message: 9 | prefix: ⬆ 10 | # Python 11 | - package-ecosystem: "pip" 12 | directory: "/" 13 | schedule: 14 | interval: "monthly" 15 | commit-message: 16 | prefix: ⬆ 17 | -------------------------------------------------------------------------------- /gca_setup_generator.py: -------------------------------------------------------------------------------- 1 | # Read the contents of setup.py 2 | with open("setup.py", "r") as file: 3 | setup_content = file.read() 4 | 5 | # Replace the project name 6 | setup_content = setup_content.replace( 7 | """name="gpt_computer_agent",""", """name="gcadev",""" 8 | ) 9 | 10 | # Write the modified content to gca_setup.py 11 | with open("gca_setup.py", "w") as file: 12 | file.write(setup_content) 13 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/stt_providers/openai_whisper_local.py: -------------------------------------------------------------------------------- 1 | model_ = None 2 | 3 | 4 | def model(): 5 | global model_ 6 | if model_ is None: 7 | import whisper 8 | 9 | model_ = whisper.load_model("tiny") 10 | return model_ 11 | 12 | 13 | def preload_stt_openai_whisper_local(): 14 | model() 15 | 16 | 17 | def stt_openai_whisper_local(audio_file): 18 | result = model().transcribe(audio_file) 19 | return result["text"] 20 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/function.py: -------------------------------------------------------------------------------- 1 | def string_to_function(func_str): 2 | # Define a local dictionary to execute the function string 3 | local_dict = {} 4 | 5 | # Execute the function string in the local dictionary 6 | exec(func_str, globals(), local_dict) 7 | 8 | # Extract the function name 9 | func_name = func_str.split("(")[0].split()[-1] 10 | 11 | # Return the function object from the local dictionary 12 | return local_dict[func_name] 13 | -------------------------------------------------------------------------------- /.github/issue-report-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "section": "Feature Requests", 4 | "labels": [ 5 | "🦄 Feature Request" 6 | ], 7 | "threshold": 100 8 | }, 9 | { 10 | "section": "Bugs", 11 | "labels": [ 12 | "🐛 Bug" 13 | ], 14 | "threshold": 100 15 | }, 16 | { 17 | "section": "Questions & Feedback", 18 | "labels": [ 19 | "🤷‍♂️ Question", 20 | "🌈 Feedback" 21 | ], 22 | "threshold": 100 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Add to Project 2 | 3 | on: 4 | pull_request_target: 5 | issues: 6 | types: 7 | - opened 8 | - reopened 9 | 10 | jobs: 11 | add-to-project: 12 | name: Add to project 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/add-to-project@v1.0.2 16 | with: 17 | project-url: https://github.com/orgs/khulnasoft/projects/11 18 | github-token: ${{ secrets.ORG_REPO_TOKEN }} 19 | -------------------------------------------------------------------------------- /refactor.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def install_refactor_tool(): 5 | os.system("pip install ruff==0.6.0") 6 | 7 | 8 | def refactor(): 9 | os.system("ruff check --fix") 10 | os.system("ruff format") 11 | 12 | 13 | def create_commit(): 14 | os.system("git add .") 15 | os.system("git commit -m 'refactor: Scheduled refactoring'") 16 | 17 | 18 | def push(): 19 | os.system("git push") 20 | 21 | 22 | if __name__ == "__main__": 23 | install_refactor_tool() 24 | refactor() 25 | create_commit() 26 | push() 27 | -------------------------------------------------------------------------------- /gpt_computer_agent/agent/background.py: -------------------------------------------------------------------------------- 1 | from langchain_core.messages import SystemMessage 2 | 3 | try: 4 | from .chat_history import * 5 | from ..llm_settings import first_message 6 | except ImportError: 7 | from agent.chat_history import * 8 | from llm_settings import first_message 9 | 10 | 11 | def llm_history_oiginal(): 12 | return [ 13 | SystemMessage( 14 | content=[ 15 | { 16 | "type": "text", 17 | "text": first_message(), 18 | } 19 | ] 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /gpt_computer_agent/version.py: -------------------------------------------------------------------------------- 1 | # Get the current dir then open __init__.py then extract the version from it 2 | 3 | # __version__ = '0.27.5' 4 | 5 | import os 6 | 7 | 8 | current_dir = os.path.dirname(os.path.abspath(__file__)) 9 | 10 | def get_version(): 11 | with open(os.path.join(current_dir, "__init__.py")) as f: 12 | for line in f: 13 | if "__version__" in line: 14 | the_str = line.split("=")[1].strip().strip("'") 15 | the_str = the_str.replace("' # fmt: skip", "") 16 | return the_str 17 | return None 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/refactor.yml: -------------------------------------------------------------------------------- 1 | name: Manual Refactor 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | run-refactor: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@v2 13 | 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: 3.8 18 | 19 | - name: setup git config 20 | run: | 21 | # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default 22 | git config user.name "KhulnaSoft Refactor Bot" 23 | git config user.email "<>" 24 | 25 | - name: Run Refactor Script 26 | run: | 27 | python refactor.py -------------------------------------------------------------------------------- /gpt_computer_agent/custom_callback.py: -------------------------------------------------------------------------------- 1 | """Callback Handler streams to stdout on new llm token.""" 2 | 3 | from langchain.callbacks.streaming_stdout_final_only import ( 4 | FinalStreamingStdOutCallbackHandler, 5 | ) 6 | from typing import Any 7 | 8 | 9 | class customcallback(FinalStreamingStdOutCallbackHandler): 10 | def on_llm_new_token(self, token: str, **kwargs: Any) -> None: 11 | self.append_to_last_tokens(token) 12 | 13 | if self.check_if_answer_reached(): 14 | self.answer_reached = True 15 | 16 | return 17 | 18 | if self.answer_reached: 19 | from .gpt_computer_agent import the_main_window 20 | 21 | the_main_window.set_text_to_input_box(token) 22 | -------------------------------------------------------------------------------- /gpt_computer_agent/tooler.py: -------------------------------------------------------------------------------- 1 | from langchain.tools import tool 2 | 3 | try: 4 | from .utils.db import load_api_key 5 | from .llm import get_model 6 | from .top_bar_wrapper import wrapper 7 | except ImportError: 8 | from top_bar_wrapper import wrapper 9 | 10 | 11 | def Tool(func): 12 | """ 13 | A decorator function to register a tool with the custom tools list. 14 | 15 | Parameters: 16 | - func (callable): The function to be registered as a tool. 17 | 18 | Returns: 19 | - callable: The input function `func` unchanged. 20 | """ 21 | from .agent.agent import custom_tools_ 22 | 23 | global custom_tools_ 24 | func = wrapper(func) 25 | custom_tools_.append(tool(func)) 26 | return func 27 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/pypi.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def install_library(library): 5 | try: 6 | result = subprocess.run( 7 | ["pip", "install", library], 8 | check=True, 9 | stdout=subprocess.PIPE, 10 | stderr=subprocess.PIPE, 11 | ) 12 | return result.returncode == 0 13 | except subprocess.CalledProcessError: 14 | return False 15 | 16 | 17 | def uninstall_library(library): 18 | try: 19 | result = subprocess.run( 20 | ["pip", "uninstall", "-y", library], 21 | check=True, 22 | stdout=subprocess.PIPE, 23 | stderr=subprocess.PIPE, 24 | ) 25 | return result.returncode == 0 26 | except subprocess.CalledProcessError: 27 | return False 28 | -------------------------------------------------------------------------------- /.github/workflows/add-comment-from-tag.yml: -------------------------------------------------------------------------------- 1 | # Based on a label applied to an issue, the bot will add a comment with some additional info 2 | name: 🎯 Auto-Reply to Labeled Tickets 3 | on: 4 | issues: 5 | types: 6 | - labeled 7 | - unlabeled 8 | pull_request_target: 9 | types: 10 | - labeled 11 | - unlabeled 12 | permissions: 13 | contents: read 14 | issues: write 15 | pull-requests: write 16 | 17 | jobs: 18 | comment: 19 | runs-on: ubuntu-20.04 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | - name: Label Commenter 24 | uses: peaceiris/actions-label-commenter@v1 25 | with: 26 | config_file: .github/issue-auto-comments.yml 27 | github_token: ${{ secrets.ORG_REPO_TOKEN || secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/auto-rebase-pr.ym: -------------------------------------------------------------------------------- 1 | # When a '/rebase' comment is added to a PR, it will be rebased from the main branch 2 | name: 🏗️ Automatic PR Rebase 3 | on: 4 | issue_comment: 5 | types: [created] 6 | jobs: 7 | rebase: 8 | name: Rebase 9 | if: > 10 | github.event.issue.pull_request != '' 11 | && contains(github.event.comment.body, '/rebase') 12 | && github.event.comment.author_association == 'MEMBER' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | with: 18 | token: ${{ secrets.ORG_REPO_TOKEN || secrets.GITHUB_TOKEN }} 19 | fetch-depth: 0 20 | - name: Rebase 21 | uses: cirrus-actions/rebase@1.4 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN || secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /gpt_computer_agent/top_bar_wrapper.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import time 3 | 4 | 5 | def wrapper(func): 6 | """A decorator that logs the start and end of the function call.""" 7 | 8 | @functools.wraps(func) 9 | def wrapped_func(*args, **kwargs): 10 | try: 11 | from .gpt_computer_agent import the_main_window 12 | 13 | print("GOOGLE-searching") 14 | function_name = "Tool: " + func.__name__ 15 | the_main_window.active_border_animation(function_name) 16 | time.sleep(2) 17 | result = func(*args, **kwargs) 18 | the_main_window.deactive_border_animation(function_name) 19 | time.sleep(1) 20 | print("GOOGLE SEARCHİNG COMPLEATES") 21 | 22 | return result 23 | except: 24 | return func(*args, **kwargs) 25 | 26 | return wrapped_func 27 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/user_id.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import os 3 | 4 | 5 | try: 6 | from .folder import currently_dir, artifacts_dir, media_dir 7 | 8 | except: 9 | from folder import currently_dir, artifacts_dir, media_dir 10 | 11 | 12 | user_id_db = os.path.join(artifacts_dir, "user_id.db") 13 | 14 | 15 | def save_user_id(): 16 | """Save a unique user ID to a file.""" 17 | with open(user_id_db, "w") as f: 18 | uuid4 = str(uuid.uuid4()) 19 | f.write(uuid4) 20 | return uuid4 21 | 22 | 23 | def load_user_id(): 24 | """Load the unique user ID from a file.""" 25 | if not os.path.exists(user_id_db): 26 | return save_user_id() 27 | with open(user_id_db, "r") as f: 28 | return f.read() 29 | 30 | 31 | def change_user_id(user_id): 32 | """Change the unique user ID.""" 33 | with open(user_id_db, "w") as f: 34 | f.write(user_id) 35 | return user_id 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_type: 7 | required: true 8 | type: choice 9 | options: 10 | - patch 11 | - minor 12 | - major 13 | 14 | permissions: 15 | contents: write 16 | jobs: 17 | run: 18 | runs-on: ubuntu-latest 19 | if: contains('["nxpkg"]', github.actor) 20 | 21 | steps: 22 | - name: Checkout Repository 23 | uses: actions/checkout@v2 24 | 25 | - name: Set Up Python 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: 3.8 29 | 30 | 31 | - name: setup git config 32 | run: | 33 | # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default 34 | git config user.name "GitHub Actions Bot" 35 | git config user.email "<>" 36 | 37 | - name: Run Version Bump Script 38 | run: python bump.py ${{ github.event.inputs.release_type }} 39 | 40 | 41 | -------------------------------------------------------------------------------- /gpt_computer_agent/agent/chat_history.py: -------------------------------------------------------------------------------- 1 | from langchain_community.chat_message_histories import SQLChatMessageHistory 2 | from .background import llm_history_oiginal 3 | 4 | try: 5 | from ..utils.db import get_history_db 6 | from ..utils.db import load_model_settings, agents 7 | from ..llm_settings import llm_settings 8 | from ..utils.chat_history import ChatHistory 9 | except ImportError: 10 | from utils.db import get_history_db 11 | from utils.db import load_model_settings 12 | from utils.chat_history import ChatHistory 13 | from llm_settings import llm_settings 14 | 15 | 16 | def get_chat_message_history(): 17 | connection = SQLChatMessageHistory( 18 | session_id="abc123", connection_string=f"sqlite:///{get_history_db()}" 19 | ) 20 | if len(connection.messages) == 0: 21 | the_model = load_model_settings() 22 | if llm_settings[the_model]["tools"]: 23 | connection.add_message(llm_history_oiginal()[0]) 24 | 25 | return connection 26 | 27 | 28 | def clear_chat_history(): 29 | ChatHistory().clear_chat() 30 | -------------------------------------------------------------------------------- /gpt_computer_agent/character.py: -------------------------------------------------------------------------------- 1 | name_ = "GPT Computer Assistant" 2 | 3 | 4 | def name(): 5 | global name_ 6 | return name_ 7 | 8 | 9 | def change_name(new_name): 10 | global name_ 11 | name_ = new_name 12 | 13 | from .gpt_computer_agent import the_main_window 14 | 15 | def adjust_string_length(input_string): 16 | if len(input_string) < 20: 17 | return input_string.ljust(20) 18 | else: 19 | return input_string[:20] 20 | 21 | the_main_window.title_label.setText(adjust_string_length(name_)) 22 | 23 | 24 | developer_ = "Open Source Community" 25 | 26 | 27 | def developer(): 28 | global developer_ 29 | return developer_ 30 | 31 | 32 | def change_developer(new_developer): 33 | global developer_ 34 | developer_ = new_developer 35 | 36 | 37 | the_website_content = None 38 | 39 | 40 | def get_website_content(): 41 | global the_website_content 42 | return the_website_content 43 | 44 | 45 | def set_website_content(content): 46 | global the_website_content 47 | the_website_content = content 48 | -------------------------------------------------------------------------------- /gpt_computer_agent/gui/signal.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSignal, QObject 2 | 3 | 4 | class SignalHandler(QObject): 5 | """ 6 | A QObject subclass to handle signals used in the GUI application. 7 | 8 | This class defines several signals that can be used to communicate 9 | between different components of the GUI application. 10 | 11 | Signals: 12 | - recording_started: Signal emitted when recording is started. 13 | - recording_stopped: Signal emitted when recording is stopped. 14 | - assistant_thinking: Signal emitted when the assistant is processing a request. 15 | - assistant_response_ready: Signal emitted when the assistant response is ready to be displayed. 16 | - assistant_response_stopped: Signal emitted when the assistant response display is stopped. 17 | 18 | """ 19 | 20 | recording_started = pyqtSignal() 21 | recording_stopped = pyqtSignal() 22 | assistant_thinking = pyqtSignal() 23 | assistant_response_ready = pyqtSignal() 24 | assistant_response_stopped = pyqtSignal() 25 | 26 | 27 | signal_handler = SignalHandler() 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 KhulnaSoft Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | langgraph==0.2.56 2 | #pyqt5==5.15.10 3 | pyqt5==5.15.* 4 | scipy==1.13.1 5 | pygame==2.5.2 6 | soundcard==0.4.3 7 | openai==1.57.0 8 | langchain-google-genai==2.0.6 9 | python-dotenv==1.0.0 10 | upsonic==0.28.4 11 | pyautogui==0.9.54 12 | sounddevice==0.4.6 13 | soundfile==0.12.1 14 | pydub==0.25.1 15 | pyscreeze==0.1.30 16 | pyperclip==1.8.2 17 | pydantic==2.10.3 18 | pillow==10.3.0 19 | langchainhub==0.1.18 20 | langchain-experimental==0.3.3 21 | opentelemetry-sdk==1.24.0 22 | opentelemetry-exporter-otlp==1.24.0 23 | langchain-groq==0.2.1 24 | langchain-openai==0.2.11 25 | langchain==0.3.10 26 | langchain-community==0.3.10 27 | langchain-core==0.3.22 28 | 29 | # custom tools 30 | pyperclip==1.8.2 31 | google==3.0.0 32 | duckduckgo-search==5.3.0 33 | beautifulsoup4==4.12.3 34 | 35 | pytesseract==0.3.10 36 | pywifi-controls==0.7 37 | 38 | pynput==1.7.7 39 | 40 | kot==0.1.2 41 | 42 | 43 | screeninfo==0.8.1 44 | 45 | anthropic==0.40.0 46 | langchain-anthropic==0.3.0 47 | 48 | StrEnum==0.4.15 49 | 50 | langchain-mcp==0.1.0a1 51 | 52 | waitress==3.0.2 53 | 54 | langchain-aws==0.2.9 55 | rich 56 | 57 | sentry-sdk==2.19.2 58 | sentry-sdk[opentelemetry] -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | **Category**: 7 | > One of: Bugfix / Feature / Code style update / Refactoring Only / Build related changes / Documentation / Other (please specify) 8 | 9 | **Overview** 10 | > Briefly outline your new changes... 11 | 12 | **Issue Number** _(if applicable)_ #00 13 | 14 | **New Vars** _(if applicable)_ 15 | > If you've added any new build scripts, environmental variables, config file options, dependency or devDependency, please outline here 16 | 17 | **Screenshot** _(if applicable)_ 18 | > If you've introduced any significant UI changes, please include a screenshot 19 | 20 | **Code Quality Checklist** _(Please complete)_ 21 | - [ ] All changes are backwards compatible 22 | - [ ] All lint checks and tests are passing 23 | - [ ] There are no (new) build warnings or errors 24 | - [ ] _(If a new config option is added)_ Attribute is outlined in the schema and documented 25 | - [ ] _(If a new dependency is added)_ Package is essential, and has been checked out for security or performance 26 | - [ ] _(If significant change)_ Bumps version in package.json 27 | -------------------------------------------------------------------------------- /gpt_computer_agent/agent/agent_tools.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ..utils.db import * 3 | from ..tooler import * 4 | from ..display_tools import * 5 | from ..teams import * 6 | from ..llm_settings import llm_settings 7 | except ImportError: 8 | from utils.db import * 9 | 10 | from tooler import * 11 | from display_tools import * 12 | from teams import * 13 | from llm_settings import llm_settings 14 | 15 | 16 | custom_tools = [] 17 | 18 | 19 | def load_tiger_tools(): 20 | try: 21 | from upsonic import Tiger 22 | 23 | tools = Tiger() 24 | tools.enable_auto_requirements = True 25 | tools = tools.langchain() 26 | return tools 27 | except: 28 | return False 29 | 30 | 31 | def load_default_tools(): 32 | from ..standard_tools import get_standard_tools 33 | 34 | return get_standard_tools() 35 | 36 | 37 | cached_tiger_tools = None 38 | 39 | 40 | def get_tiger_tools(): 41 | global cached_tiger_tools 42 | if cached_tiger_tools is None: 43 | cached_tiger_tools = load_tiger_tools() 44 | return cached_tiger_tools 45 | 46 | 47 | if is_online_tools_setting_active(): 48 | get_tiger_tools() 49 | 50 | 51 | def get_tools(): 52 | return [] 53 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | langgraph==0.2.56 2 | #pyqt5==5.15.10 3 | pyqt5==5.15.* 4 | scipy==1.13.1 5 | pygame==2.5.2 6 | soundcard==0.4.3 7 | openai==1.57.0 8 | langchain-google-genai==2.0.6 9 | python-dotenv==1.0.0 10 | upsonic==0.28.4 11 | pyautogui==0.9.54 12 | sounddevice==0.4.6 13 | soundfile==0.12.1 14 | pydub==0.25.1 15 | pyscreeze==0.1.30 16 | pyperclip==1.8.2 17 | pydantic==2.10.3 18 | pillow==10.3.0 19 | langchainhub==0.1.18 20 | langchain-experimental==0.3.3 21 | opentelemetry-sdk==1.24.0 22 | opentelemetry-exporter-otlp==1.24.0 23 | langchain-groq==0.2.1 24 | langchain-openai==0.2.11 25 | langchain==0.3.10 26 | langchain-community==0.3.10 27 | langchain-core==0.3.22 28 | 29 | # custom tools 30 | pyperclip==1.8.2 31 | google==3.0.0 32 | duckduckgo-search==5.3.0 33 | beautifulsoup4==4.12.3 34 | 35 | pytesseract==0.3.10 36 | pywifi-controls==0.7 37 | 38 | pynput==1.7.7 39 | 40 | kot==0.1.2 41 | 42 | 43 | screeninfo==0.8.1 44 | 45 | anthropic==0.40.0 46 | langchain-anthropic==0.3.0 47 | 48 | 49 | 50 | StrEnum==0.4.15 51 | 52 | 53 | langchain-mcp==0.1.0a1 54 | 55 | 56 | waitress==3.0.2 57 | 58 | langchain-aws==0.2.9 59 | 60 | rich 61 | 62 | sentry-sdk==2.19.2 63 | sentry-sdk[opentelemetry] -------------------------------------------------------------------------------- /gpt_computer_agent/agentic.py: -------------------------------------------------------------------------------- 1 | from .utils.db import agents 2 | 3 | 4 | class Agent: 5 | """ 6 | Represents an agent within the system. 7 | 8 | This class defines an agent with a specific role, goal, and backstory. Upon initialization, 9 | the agent is added to the global list of agents. 10 | 11 | Attributes: 12 | - role (str): The role of the agent. 13 | - goal (str): The goal or objective of the agent. 14 | - backstory (str): The backstory or history of the agent. 15 | 16 | Methods: 17 | - __init__(role, goal, backstory): Initializes the Agent object and adds it to the global list of agents. 18 | 19 | Global Variables: 20 | - agents (list): A global list containing information about all agents in the system. 21 | """ 22 | 23 | def __init__(self, role, goal, backstory): 24 | """ 25 | Initializes a new Agent object and adds it to the global list of agents. 26 | 27 | Parameters: 28 | - role (str): The role of the agent. 29 | - goal (str): The goal or objective of the agent. 30 | - backstory (str): The backstory or history of the agent. 31 | 32 | Returns: 33 | None 34 | """ 35 | global agents 36 | agents.append({"role": role, "goal": goal, "backstory": backstory}) 37 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/wake_word.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | try: 4 | from ..utils.db import load_pvporcupine_api_key 5 | except ImportError: 6 | from utils.db import load_pvporcupine_api_key 7 | 8 | 9 | def wake_word(the_main_window): 10 | import pvporcupine 11 | import pyaudio 12 | 13 | porcupine = pvporcupine.create( 14 | access_key=load_pvporcupine_api_key(), keywords=pvporcupine.KEYWORDS 15 | ) 16 | # Initialize PyAudio 17 | pa = pyaudio.PyAudio() 18 | 19 | # Open an audio stream 20 | audio_stream = pa.open( 21 | rate=porcupine.sample_rate, 22 | channels=1, 23 | format=pyaudio.paInt16, 24 | input=True, 25 | frames_per_buffer=porcupine.frame_length, 26 | ) 27 | 28 | print("Listening for wake word...") 29 | 30 | # Continuously listen for the wake word 31 | while the_main_window.wake_word_active: 32 | pcm = audio_stream.read(porcupine.frame_length) 33 | pcm = struct.unpack_from("h" * porcupine.frame_length, pcm) 34 | 35 | # Process the audio frame and check for the wake word 36 | keyword_index = porcupine.process(pcm) 37 | 38 | if keyword_index >= 0: 39 | print("Wake word detected!") 40 | return True 41 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/tts_providers/microsoft_local.py: -------------------------------------------------------------------------------- 1 | import soundfile as sf 2 | 3 | 4 | synthesiser_ = None 5 | 6 | 7 | def synthesiser(): 8 | from transformers import pipeline 9 | 10 | global synthesiser_ 11 | if synthesiser_ is None: 12 | synthesiser_ = pipeline("text-to-speech", "microsoft/speecht5_tts") 13 | return synthesiser_ 14 | 15 | 16 | embeddings_dataset_ = None 17 | 18 | 19 | def embeddings_dataset(): 20 | from datasets import load_dataset 21 | 22 | global embeddings_dataset_ 23 | if embeddings_dataset_ is None: 24 | embeddings_dataset_ = load_dataset( 25 | "Matthijs/cmu-arctic-xvectors", split="validation" 26 | ) 27 | return embeddings_dataset_ 28 | 29 | 30 | speaker_embedding_ = None 31 | 32 | 33 | def speaker_embedding(): 34 | import torch 35 | 36 | global speaker_embedding_ 37 | if speaker_embedding_ is None: 38 | speaker_embedding_ = torch.tensor( 39 | embeddings_dataset()[7306]["xvector"] 40 | ).unsqueeze(0) 41 | return speaker_embedding_ 42 | 43 | 44 | def preload_tts_microsoft_local(): 45 | synthesiser() 46 | embeddings_dataset() 47 | speaker_embedding() 48 | 49 | 50 | def tts_microsoft_local(text_chunk, location): 51 | speech = synthesiser()( 52 | text_chunk, forward_params={"speaker_embeddings": speaker_embedding()} 53 | ) 54 | sf.write(location, speech["audio"], samplerate=speech["sampling_rate"]) 55 | return location 56 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/telemetry.py: -------------------------------------------------------------------------------- 1 | from opentelemetry.sdk.resources import Resource 2 | from opentelemetry.sdk.trace.export import ( 3 | BatchSpanProcessor, 4 | ) 5 | from opentelemetry import trace 6 | from opentelemetry.sdk.trace import TracerProvider 7 | 8 | from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter 9 | 10 | 11 | def CreateTracer(service_name, trace_name, infrastackai_api_key=None): 12 | tracer = trace.get_tracer(trace_name) 13 | resource = Resource.create({"service.name": service_name}) 14 | provider = TracerProvider(resource=resource) 15 | trace.set_tracer_provider(provider) 16 | 17 | provider.add_span_processor( 18 | BatchSpanProcessor( 19 | OTLPSpanExporter( 20 | endpoint="https://collector-us1-http.infrastack.ai/v1/traces", 21 | headers=(("infrastack-api-key", infrastackai_api_key),), 22 | ) 23 | ) 24 | ) 25 | 26 | return tracer 27 | 28 | 29 | def os_name(): 30 | import platform 31 | 32 | system_name = platform.system() 33 | if system_name == "Windows": 34 | return "Windows" 35 | elif system_name == "Darwin": 36 | return "macOS" 37 | elif system_name == "Linux": 38 | return "Linux" 39 | else: 40 | return "Unknown OS" 41 | 42 | 43 | my_tracer = CreateTracer( 44 | "gpt_computer_agent", 45 | "app", 46 | infrastackai_api_key="sk-2b29c6da910d2883de0599d4c5dd6b9d2e4ec61bbfa834d5", 47 | ) 48 | -------------------------------------------------------------------------------- /example_use_cases/workday_summerizer.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | In this example we have an idea to summerize whole day of an employee via GPT Computer Assistant. 3 | 4 | 5 | 6 | # Code 7 | ```console 8 | computeragent --api 9 | ``` 10 | 11 | 12 | ```python 13 | from gpt_computer_agent.remote import remote 14 | 15 | 16 | 17 | remote.profile("Screen Analysis") 18 | 19 | # We will loop for 5 minutes 20 | 21 | loop_results = [] 22 | 23 | 24 | for i in range(1000): 25 | remote.reset_memory() 26 | 27 | remote.just_screenshot() 28 | 29 | detailed_analyses = remote.input("What is in the scren, detailed analyses") 30 | app_name = remote.input("What is the app that the employee is using?") 31 | subject = remote.input("What is the subject of this usage of the app?") 32 | activity = remote.input("What is the employee doing now?") 33 | loop_results.append({"detailed_analyses": detailed_analyses, "app_name": app_name, "subject": subject, "activity": activity}) 34 | 35 | 36 | remote.wait(10) 37 | 38 | 39 | # Summery of the work day 40 | 41 | summery_results = [] 42 | 43 | remote.profile("Summerizer") 44 | remote.reset_memory() 45 | for i in loop_results: 46 | 47 | total_string = i["detailed_analyses"] + " " + i["app_name"] + " " + i["subject"] + " " + i["activity"] 48 | total_string = "Please summerize the work day" + total_string 49 | summerized = remote.input(total_string) 50 | summery_results.append(summerized) 51 | 52 | 53 | print("Summery: ", summery_results) 54 | 55 | ``` -------------------------------------------------------------------------------- /gpt_computer_agent/screen/shot.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import pyautogui 3 | 4 | try: 5 | from ..gui.signal import signal_handler 6 | from ..utils.db import just_screenshot_path 7 | from ..cu.computer import screenshot_action_ 8 | except ImportError: 9 | from gui.signal import signal_handler 10 | from utils.db import just_screenshot_path 11 | from cu.computer import screenshot_action_ 12 | 13 | 14 | def encode_image(image_path): 15 | """ 16 | Encode an image file to base64 format. 17 | 18 | Parameters: 19 | - image_path (str): The path to the image file to encode. 20 | 21 | Returns: 22 | - str or None: The base64 encoded string of the image, or None if an error occurs. 23 | """ 24 | try: 25 | with open(image_path, "rb") as image_file: 26 | return base64.b64encode(image_file.read()).decode("utf-8") 27 | except FileNotFoundError: 28 | print(f"File not found: {image_path}") 29 | return None 30 | except Exception as e: 31 | print(f"An error occurred while encoding the image: {e}") 32 | return None 33 | 34 | 35 | def take_screenshot(): 36 | """ 37 | Take a screenshot using pyautogui and save it. 38 | 39 | This function takes a screenshot of the entire screen using pyautogui, 40 | saves it to the specified path, and emits a signal indicating that 41 | the assistant is thinking. 42 | 43 | Returns: 44 | - None 45 | """ 46 | try: 47 | screenshot_action_(just_screenshot_path) 48 | signal_handler.assistant_thinking.emit() 49 | except Exception as e: 50 | print(f"An error occurred while taking the screenshot: {e}") 51 | -------------------------------------------------------------------------------- /gpt_computer_agent/cu/run.py: -------------------------------------------------------------------------------- 1 | """Utility to run shell commands asynchronously with a timeout.""" 2 | 3 | import asyncio 4 | 5 | TRUNCATED_MESSAGE: str = "To save on context only part of this file has been shown to you. You should retry this tool after you have searched inside the file with `grep -n` in order to find the line numbers of what you are looking for." 6 | MAX_RESPONSE_LEN: int = 16000 7 | 8 | 9 | def maybe_truncate(content: str, truncate_after: int | None = MAX_RESPONSE_LEN): 10 | """Truncate content and append a notice if content exceeds the specified length.""" 11 | return ( 12 | content 13 | if not truncate_after or len(content) <= truncate_after 14 | else content[:truncate_after] + TRUNCATED_MESSAGE 15 | ) 16 | 17 | 18 | async def run( 19 | cmd: str, 20 | timeout: float | None = 120.0, # seconds 21 | truncate_after: int | None = MAX_RESPONSE_LEN, 22 | ): 23 | """Run a shell command asynchronously with a timeout.""" 24 | process = await asyncio.create_subprocess_shell( 25 | cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 26 | ) 27 | 28 | try: 29 | stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout) 30 | return ( 31 | process.returncode or 0, 32 | maybe_truncate(stdout.decode(), truncate_after=truncate_after), 33 | maybe_truncate(stderr.decode(), truncate_after=truncate_after), 34 | ) 35 | except asyncio.TimeoutError as exc: 36 | try: 37 | process.kill() 38 | except ProcessLookupError: 39 | pass 40 | raise TimeoutError( 41 | f"Command '{cmd}' timed out after {timeout} seconds" 42 | ) from exc -------------------------------------------------------------------------------- /gpt_computer_agent/utils/train.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | 4 | try: 5 | from ..character import set_website_content 6 | except: 7 | from gpt_computer_agent.character import set_website_content 8 | 9 | 10 | def train(url: str) -> bool: 11 | try: 12 | # Go to url and extract these elements 13 | meta_properties = [ 14 | "og:description", 15 | "og:site_name", 16 | "og:title", 17 | "og:type", 18 | "og:url", 19 | ] 20 | 21 | # Fetch the webpage content 22 | response = requests.get(url) 23 | soup = BeautifulSoup(response.content, "html.parser") 24 | 25 | # Extract the meta tags 26 | meta_tags = soup.find_all("meta") 27 | 28 | # Initialize the data dictionary 29 | data = {} 30 | 31 | # Loop through the meta tags and extract the content 32 | for tag in meta_tags: 33 | if tag.get("property") in meta_properties: 34 | data[tag.get("property")] = tag.get("content") 35 | 36 | # Also add the other useful information texts from the webpage 37 | data["title"] = soup.title.string 38 | data["h1"] = soup.h1.string 39 | data["p"] = soup.p.string 40 | 41 | text = soup.get_text(separator="\n", strip=True) 42 | 43 | data["text"] = text 44 | 45 | data["url"] = url 46 | 47 | # Now create an string with good looking like this 48 | # Title: {title} 49 | 50 | the_string = "" 51 | 52 | for key, value in data.items(): 53 | the_string += f"{key}: {value}\n" 54 | 55 | set_website_content(the_string) 56 | 57 | return True 58 | 59 | except Exception as e: 60 | return e 61 | -------------------------------------------------------------------------------- /.github/workflows/requirement_controller.yml: -------------------------------------------------------------------------------- 1 | name: Requirement Controller 2 | 3 | on: 4 | push: 5 | paths: 6 | - '.github/workflows/requirement_controller.yml' 7 | - 'requirements.txt' 8 | pull_request: 9 | paths: 10 | - '.github/workflows/requirement_controller.yml' 11 | - 'requirements.txt' 12 | 13 | workflow_dispatch: 14 | 15 | 16 | 17 | 18 | jobs: 19 | 20 | windows: 21 | runs-on: windows-latest 22 | steps: 23 | - name: Checkout Repository 24 | uses: actions/checkout@v2 25 | 26 | - name: Set Up Python 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: 3.11 30 | 31 | - name: Install Dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install .[default] 35 | pip install .[agentic] 36 | 37 | 38 | 39 | 40 | macos: 41 | runs-on: macos-latest 42 | steps: 43 | - name: Checkout Repository 44 | uses: actions/checkout@v2 45 | 46 | - name: Set Up Python 47 | uses: actions/setup-python@v2 48 | with: 49 | python-version: 3.11 50 | 51 | - name: Install Dependencies 52 | run: | 53 | python -m pip install --upgrade pip 54 | pip install .[default] 55 | pip install .[agentic] 56 | 57 | 58 | 59 | linux: 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: Checkout Repository 63 | uses: actions/checkout@v2 64 | 65 | - name: Set Up Python 66 | uses: actions/setup-python@v2 67 | with: 68 | python-version: 3.11 69 | 70 | - name: Install Dependencies 71 | run: | 72 | python -m pip install --upgrade pip 73 | pip install .[default] 74 | pip install .[agentic] 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/showcase-addition.yml: -------------------------------------------------------------------------------- 1 | name: Add your Dashboard to the Showcase 🌟 2 | description: Share a screenshot of your dashboard to the Readme showcase! 3 | title: '[SHOWCASE] ' 4 | labels: ['💯 Showcase'] 5 | assignees: 6 | - khulnasoft 7 | - NxPkg 8 | 9 | body: 10 | # 1 - Title 11 | - type: input 12 | id: title 13 | attributes: 14 | label: Title 15 | description: Pick a title for your addition 16 | placeholder: My Awesome Dashboard 17 | validations: 18 | required: false 19 | # 2 - Link to Screenshot 20 | - type: textarea 21 | id: screenshot 22 | attributes: 23 | label: Screenshot 24 | description: Either upload your screenshot here, or include a link to a png/jpg on a CDN / image hosting service 25 | validations: 26 | required: true 27 | # 3 - Credit user 28 | - type: dropdown 29 | id: attribution 30 | attributes: 31 | label: Would you like your name/ username included? 32 | description: This will be displayed above the screenshot to your dashboard in the showcase page 33 | options: 34 | - 'Yes' 35 | - 'No' 36 | validations: 37 | required: true 38 | # 4 - Social links 39 | - type: input 40 | id: links 41 | attributes: 42 | label: Link to your Website/ Profile/ Twitter (optional) 43 | description: You can optionally have your name link to your profile or website. If you'd like this, include the URL to your site below 44 | validations: 45 | required: false 46 | # 5 - Description 47 | - type: textarea 48 | id: description 49 | attributes: 50 | label: Description (Optional) 51 | description: You can optionally also include a short description. If there's anything else you'd like to include, then put it here 52 | validations: 53 | required: false 54 | # 6 - All done 55 | - type: markdown 56 | attributes: 57 | value: |- 58 | ## That's It! 59 | Thanks for sharing your dashboard :) You will receive an update to this ticket once it's added to the showcase 60 | validations: 61 | required: false 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request ✨ 2 | description: Suggest an idea for future development of GPT-Computer-Agent 3 | title: '[FEATURE_REQUEST] <title>' 4 | labels: ['🦄 Feature Request'] 5 | assignees: 6 | - NxPkg 7 | 8 | body: 9 | 10 | # Field 1 - Is it bug-related 11 | - type: textarea 12 | id: issue 13 | attributes: 14 | label: Is your feature request related to a problem? If so, please describe. 15 | description: 16 | placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 17 | validations: 18 | required: false 19 | 20 | # Field 2 - Describe feature 21 | - type: textarea 22 | id: solution 23 | attributes: 24 | label: Describe the solution you'd like 25 | placeholder: An outline of how you would like this to be implemented, include as much details as possible 26 | validations: 27 | required: true 28 | 29 | # Field 3 - Priority 30 | - type: dropdown 31 | id: priority 32 | attributes: 33 | label: Priority 34 | description: How urgent is the development of this feature 35 | options: 36 | - Low (Nice-to-have) 37 | - Medium (Would be very useful) 38 | - High (The app does not function without it) 39 | validations: 40 | required: true 41 | 42 | # Field 3 - Can the user implement 43 | - type: dropdown 44 | id: canImplement 45 | attributes: 46 | label: Is this something you would be keen to implement 47 | description: Are you raising this ticket in order to get an issue number for your PR? 48 | options: 49 | - 'No' 50 | - 'Maybe' 51 | - 'Yes!' 52 | validations: 53 | required: false 54 | 55 | # Final text 56 | - type: markdown 57 | attributes: 58 | value: |- 59 | ## Thanks 🙏 60 | Thank you for your feature suggestion, you should expect a reply within 48 hours :) 61 | Please note that there is no guarantee that your idea will be implemented 62 | If you haven't already done so, please Star the GPT-Computer-Agent's repository on GitHub, to help other users discover it 63 | validations: 64 | required: false 65 | -------------------------------------------------------------------------------- /gpt_computer_agent/cu/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | from dataclasses import dataclass, fields, replace 3 | from typing import Any 4 | 5 | from anthropic.types.beta import BetaToolUnionParam 6 | 7 | 8 | class BaseAnthropicTool(metaclass=ABCMeta): 9 | """Abstract base class for Anthropic-defined tools.""" 10 | 11 | @abstractmethod 12 | def __call__(self, **kwargs) -> Any: 13 | """Executes the tool with the given arguments.""" 14 | ... 15 | 16 | @abstractmethod 17 | def to_params( 18 | self, 19 | ) -> BetaToolUnionParam: 20 | raise NotImplementedError 21 | 22 | 23 | @dataclass(kw_only=True, frozen=True) 24 | class ToolResult: 25 | """Represents the result of a tool execution.""" 26 | 27 | output: str | None = None 28 | error: str | None = None 29 | base64_image: str | None = None 30 | system: str | None = None 31 | 32 | def __bool__(self): 33 | return any(getattr(self, field.name) for field in fields(self)) 34 | 35 | def __add__(self, other: "ToolResult"): 36 | def combine_fields( 37 | field: str | None, other_field: str | None, concatenate: bool = True 38 | ): 39 | if field and other_field: 40 | if concatenate: 41 | return field + other_field 42 | raise ValueError("Cannot combine tool results") 43 | return field or other_field 44 | 45 | return ToolResult( 46 | output=combine_fields(self.output, other.output), 47 | error=combine_fields(self.error, other.error), 48 | base64_image=combine_fields(self.base64_image, other.base64_image, False), 49 | system=combine_fields(self.system, other.system), 50 | ) 51 | 52 | def replace(self, **kwargs): 53 | """Returns a new ToolResult with the given fields replaced.""" 54 | return replace(self, **kwargs) 55 | 56 | 57 | class CLIResult(ToolResult): 58 | """A ToolResult that can be rendered as a CLI output.""" 59 | 60 | 61 | class ToolFailure(ToolResult): 62 | """A ToolResult that represents a failure.""" 63 | 64 | 65 | class ToolError(Exception): 66 | """Raised when a tool encounters an error.""" 67 | 68 | def __init__(self, message): 69 | self.message = message -------------------------------------------------------------------------------- /gca_setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | import platform 6 | 7 | 8 | # Read the requirements from the requirements.txt file 9 | with open("requirements.txt") as fp: 10 | install_requires = fp.read().splitlines() 11 | 12 | if platform.system() in ["Windows"]: 13 | install_requires.append("AppOpener==1.7") 14 | 15 | elif platform.system() == "Darwin": # Darwin is the system name for macOS 16 | install_requires.append( 17 | "MacAppOpener==0.0.5" 18 | ) # Replace with actual macOS specific package 19 | 20 | 21 | setup( 22 | name="gcadev", 23 | version="0.28.3", 24 | description="""GPT""", 25 | long_description="".join(open("README.md", encoding="utf-8").readlines()), 26 | long_description_content_type="text/markdown", 27 | url="https://github.com/khulnasoft/gpt-computer-agent", 28 | author="KhulnaSoft DevOps", 29 | author_email="info@khulnasoft.com", 30 | license="MIT", 31 | packages=[ 32 | "gpt_computer_agent", 33 | "gpt_computer_agent.agent", 34 | "gpt_computer_agent.cu", 35 | "gpt_computer_agent.mcp", 36 | "gpt_computer_agent.gui", 37 | "gpt_computer_agent.screen", 38 | "gpt_computer_agent.utils", 39 | "gpt_computer_agent.audio", 40 | "gpt_computer_agent.audio.tts_providers", 41 | "gpt_computer_agent.audio.stt_providers", 42 | ], 43 | include_package_data=True, 44 | install_requires=["requests==2.32.3", "matplotlib==3.9.0", "sentry-sdk==2.19.2", "sentry-sdk[opentelemetry]"], 45 | entry_points={ 46 | "console_scripts": ["computeragent=gpt_computer_agent.start:start"], 47 | }, 48 | python_requires=">= 3.9", 49 | zip_safe=False, 50 | extras_require={ 51 | "base": install_requires, 52 | "default": install_requires, 53 | "agentic": ["crewai==0.30.11"], 54 | "wakeword": ["pvporcupine", "pyaudio"], 55 | "api": [ 56 | "flask==3.0.3", 57 | ], 58 | "display": [ 59 | 60 | "opencv-python", 61 | ], 62 | "local_tts": [ 63 | "tensorflow==2.17.0", 64 | "datasets[audio]==2.20.0", 65 | "sentencepiece==0.2.0", 66 | "torch==2.4.0", 67 | "transformers==4.43.3", 68 | ], 69 | "local_stt": [ 70 | "openai-whisper==20231117", 71 | ], 72 | }, 73 | ) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | import platform 6 | 7 | 8 | # Read the requirements from the requirements.txt file 9 | with open("requirements.txt") as fp: 10 | install_requires = fp.read().splitlines() 11 | 12 | if platform.system() in ["Windows"]: 13 | install_requires.append("AppOpener==1.7") 14 | 15 | elif platform.system() == "Darwin": # Darwin is the system name for macOS 16 | install_requires.append( 17 | "MacAppOpener==0.0.5" 18 | ) # Replace with actual macOS specific package 19 | 20 | 21 | setup( 22 | name="gpt_computer_agent", 23 | version="0.28.3", 24 | description="""GPT""", 25 | long_description="".join(open("README.md", encoding="utf-8").readlines()), 26 | long_description_content_type="text/markdown", 27 | url="https://github.com/khulnasoft/gpt-computer-agent", 28 | author="KhulnaSoft DevOps", 29 | author_email="info@khulnasoft.com", 30 | license="MIT", 31 | packages=[ 32 | "gpt_computer_agent", 33 | "gpt_computer_agent.agent", 34 | "gpt_computer_agent.cu", 35 | "gpt_computer_agent.mcp", 36 | "gpt_computer_agent.gui", 37 | "gpt_computer_agent.screen", 38 | "gpt_computer_agent.utils", 39 | "gpt_computer_agent.audio", 40 | "gpt_computer_agent.audio.tts_providers", 41 | "gpt_computer_agent.audio.stt_providers", 42 | ], 43 | include_package_data=True, 44 | install_requires=["requests==2.32.3", "matplotlib==3.9.0", "sentry-sdk==2.19.2", "sentry-sdk[opentelemetry]"], 45 | entry_points={ 46 | "console_scripts": ["computeragent=gpt_computer_agent.start:start"], 47 | }, 48 | python_requires=">= 3.9", 49 | zip_safe=False, 50 | extras_require={ 51 | "base": install_requires, 52 | "default": install_requires, 53 | "agentic": ["crewai==0.30.11"], 54 | "wakeword": ["pvporcupine", "pyaudio"], 55 | "api": [ 56 | "flask==3.0.3", 57 | ], 58 | "display": [ 59 | 60 | "opencv-python", 61 | ], 62 | "local_tts": [ 63 | "tensorflow==2.17.0", 64 | "datasets[audio]==2.20.0", 65 | "sentencepiece==0.2.0", 66 | "torch==2.4.0", 67 | "transformers==4.43.3", 68 | ], 69 | "local_stt": [ 70 | "openai-whisper==20231117", 71 | ], 72 | }, 73 | ) -------------------------------------------------------------------------------- /gpt_computer_agent/agent/agent.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ..llm import get_model 3 | from ..utils.db import * 4 | from ..llm_settings import llm_settings 5 | from ..tooler import * 6 | from ..display_tools import * 7 | from ..cu.computer import * 8 | from ..teams import * 9 | from .agent_tools import get_tools 10 | from ..mcp.tool import mcp_tools 11 | from ..standard_tools import get_standard_tools 12 | 13 | except ImportError: 14 | from llm import get_model 15 | from utils.db import * 16 | from llm_settings import llm_settings 17 | from tooler import * 18 | from display_tools import * 19 | from cu.computer import * 20 | from teams import * 21 | from agent.agent_tools import get_tools 22 | from mcp.tool import mcp_tools 23 | from standard_tools import get_standard_tools 24 | 25 | 26 | from langgraph.prebuilt import create_react_agent 27 | 28 | 29 | custom_tools_ = [] 30 | 31 | 32 | def custom_tools(): 33 | global custom_tools_ 34 | the_list = [] 35 | the_list += custom_tools_ 36 | return the_list 37 | 38 | 39 | prompt_cache = {} 40 | 41 | 42 | def get_prompt(name): 43 | global prompt_cache 44 | if name in prompt_cache: 45 | return prompt_cache[name] 46 | else: 47 | from langchain import hub 48 | 49 | prompt = hub.pull(name) 50 | prompt_cache[name] = prompt 51 | return prompt 52 | 53 | 54 | def get_agent_executor(the_anthropic_model=False, no_tools=False): 55 | tools = get_tools() 56 | tools += custom_tools() 57 | 58 | model = load_model_settings() 59 | 60 | if is_predefined_agents_setting_active() and llm_settings[model]["tools"]: 61 | try: 62 | import crewai 63 | 64 | tools += [search_on_internet_and_report_team, generate_code_with_aim_team] 65 | except ImportError: 66 | pass 67 | 68 | 69 | if the_anthropic_model: 70 | tools += [] 71 | if load_aws_access_key_id() == "default": 72 | model_catch = get_model(the_model="claude-3-5-sonnet-20241022") 73 | else: 74 | model_catch = get_model(the_model="us.anthropic.claude-3-5-sonnet-20241022-v2:0") 75 | 76 | print("Anthropic model catch", model_catch) 77 | print("Anthropic tools len", len(tools)) 78 | return create_react_agent(model_catch, tools) 79 | else: 80 | tools += [mouse_scroll, click_to_text, click_to_icon, click_to_area] + mcp_tools() + get_standard_tools() 81 | 82 | 83 | 84 | if no_tools: 85 | tools = [] 86 | 87 | 88 | return create_react_agent(get_model(), tools) 89 | 90 | 91 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/stt.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ..llm import get_client 3 | from ..utils.db import * 4 | from .stt_providers.openai import stt_openai 5 | from .stt_providers.openai_whisper_local import stt_openai_whisper_local 6 | except ImportError: 7 | from utils.db import * 8 | from audio.stt_providers.openai import stt_openai 9 | from audio.stt_providers.openai_whisper_local import stt_openai_whisper_local 10 | 11 | import os 12 | from pydub import AudioSegment 13 | 14 | 15 | def is_local_stt_available(): 16 | try: 17 | return True 18 | except: 19 | return False 20 | 21 | 22 | def split_audio(file_path, max_size=20 * 1024 * 1024): 23 | """Split an audio file into smaller parts if it exceeds a maximum size. 24 | 25 | Args: 26 | file_path (str): The path to the audio file to be split. 27 | max_size (int): The maximum size in bytes for each split part. Defaults to 20 MB. 28 | 29 | Returns: 30 | list: A list of tuples containing the split audio segments and their respective file paths. 31 | """ 32 | audio = AudioSegment.from_wav(file_path) 33 | file_size = os.path.getsize(file_path) 34 | if file_size <= max_size: 35 | return [(audio, file_path)] 36 | 37 | # Calculate the number of parts needed 38 | num_parts = file_size // max_size + 1 39 | part_length = len(audio) // num_parts 40 | parts = [] 41 | 42 | for i in range(num_parts): 43 | start = i * part_length 44 | end = (i + 1) * part_length if (i + 1) < num_parts else len(audio) 45 | part = audio[start:end] 46 | part_path = f"{file_path[:-4]}_part_{i+1}.wav" 47 | part.export(part_path, format="wav") 48 | parts.append((part, part_path)) 49 | 50 | return parts 51 | 52 | 53 | def speech_to_text(location): 54 | """Convert speech audio file to text using an external service. 55 | 56 | Args: 57 | location (str): The path to the speech audio file. 58 | 59 | Returns: 60 | str: The transcribed text from the speech audio file. 61 | """ 62 | audio_parts = split_audio(location) 63 | transcriptions = [] 64 | 65 | for part, part_path in audio_parts: 66 | with open(part_path, "rb") as audio_file: 67 | if load_stt_model_settings() == "openai": 68 | transcription = stt_openai(audio_file) 69 | else: 70 | transcription = stt_openai_whisper_local(part_path) 71 | 72 | transcriptions.append(transcription) 73 | os.remove(part_path) # Clean up the temporary file immediately after processing 74 | 75 | # Merge transcriptions (assuming it's a list of text segments) 76 | full_transcription = " ".join(transcription for transcription in transcriptions) 77 | return full_transcription 78 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Question 🤷‍♂️ 2 | description: Got a question about GPT-Computer-Agent, deployment, development or usage? 3 | title: '[QUESTION] <title>' 4 | labels: ['🤷‍♂️ Question'] 5 | assignees: 6 | - NxPkg 7 | 8 | body: 9 | # Filed 1 - Intro Text 10 | - type: markdown 11 | attributes: 12 | value: > 13 | Thanks for using GPT-Computer-Agent! Questions are welcome, but in the future will be moving over to 14 | [Discussions](https://github.com/khulnaSoft/gpt-computer-agent/discussions) page. 15 | Quick questions should be asked [here](https://github.com/khulnaSoft/gpt-computer-agent/discussions/148) instead. 16 | validations: 17 | required: false 18 | 19 | # Field 2 - The actual question 20 | - type: textarea 21 | id: question 22 | attributes: 23 | label: Question 24 | description: Outline your question in a clear and concise manner 25 | validations: 26 | required: true 27 | 28 | # Field 3 - Category 29 | - type: dropdown 30 | id: category 31 | attributes: 32 | label: Category 33 | description: What part of the application does this relate to? 34 | options: 35 | - Setup and Deployment 36 | - Configuration 37 | - App Usage 38 | - Development 39 | - Documentation 40 | - Alternate Views 41 | - Authentication 42 | - Using Icons 43 | - Widgets 44 | - Actions 45 | - Language Support 46 | - Search & Shortcuts 47 | - Status Checking 48 | - Theming & Layout 49 | validations: 50 | required: true 51 | 52 | # Field 4 - User has RTFM first, and agrees to code of conduct, etc 53 | - type: checkboxes 54 | id: idiot-check 55 | attributes: 56 | label: Please tick the boxes 57 | description: Before submitting, please ensure that 58 | options: 59 | - label: You are using a [supported](https://github.com/khulnaSoft/gpt-computer-agent/blob/master/.github/SECURITY.md#supported-versions) version of GPT-Computer-Agent (check the first two digits of the version number) 60 | required: true 61 | - label: You've checked that this [question hasn't already been raised](https://github.com/khulnaSoft/gpt-computer-agent/issues?q=is%3Aissue) 62 | required: true 63 | - label: You've checked the [docs](https://github.com/khulnaSoft/gpt-computer-agent/tree/master/docs#readme) and [troubleshooting](https://github.com/khulnaSoft/gpt-computer-agent/blob/master/docs/troubleshooting.md#troubleshooting) guide 64 | required: true 65 | - label: You agree to the [code of conduct](https://github.com/khulnaSoft/gpt-computer-agent/blob/master/.github/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct) 66 | required: true 67 | -------------------------------------------------------------------------------- /gpt_computer_agent/cu/ask_anthropic.py: -------------------------------------------------------------------------------- 1 | import random 2 | import traceback 3 | 4 | 5 | from langchain_core.messages import HumanMessage, SystemMessage, AIMessage 6 | 7 | 8 | 9 | 10 | try: 11 | 12 | from ..agent import get_agent_executor 13 | from ..screen.shot import * 14 | from ..utils.db import load_model_settings, agents 15 | from ..llm import get_model 16 | from ..llm_settings import llm_settings 17 | from ..utils.chat_history import ChatHistory 18 | from .computer import screenshot_action 19 | except ImportError: 20 | 21 | from agent import get_agent_executor 22 | from screen.shot import * 23 | from utils.db import load_model_settings, agents 24 | from llm import get_model 25 | from llm_settings import llm_settings 26 | from utils.chat_history import ChatHistory 27 | from computer import screenshot_action 28 | 29 | 30 | 31 | 32 | 33 | 34 | def ask_anthropic( 35 | the_request:str 36 | ): 37 | 38 | try: 39 | 40 | from ..agent import get_agent_executor 41 | except ImportError: 42 | from agent import get_agent_executor 43 | 44 | 45 | try: 46 | print("ASK ANTHROPIC", the_request) 47 | 48 | 49 | 50 | llm_input = the_request 51 | 52 | print("LLM INPUT", llm_input) 53 | 54 | 55 | 56 | 57 | human_first_message = {"type": "text", "text": f"{llm_input}"} 58 | 59 | 60 | 61 | the_message = [ 62 | human_first_message 63 | ] 64 | 65 | 66 | 67 | human_second_message = None 68 | 69 | 70 | 71 | base64_image = screenshot_action(direct_base64=True) 72 | 73 | 74 | 75 | 76 | 77 | human_second_message = { 78 | "type": "image_url", 79 | "image_url": {"url": f"data:image/png;base64,{base64_image}"}, 80 | } 81 | 82 | 83 | 84 | print("LEN OF IMAGE", len(base64_image)) 85 | 86 | 87 | if human_second_message: 88 | the_message.append(human_second_message) 89 | 90 | 91 | 92 | 93 | 94 | the_message = HumanMessage(content=the_message) 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | msg = get_agent_executor(the_anthropic_model=True).invoke( 106 | {"messages": [the_message]} 107 | ) 108 | 109 | 110 | 111 | 112 | 113 | the_last_messages = msg["messages"] 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | return_value = the_last_messages[-1].content 124 | if isinstance(return_value, list): 125 | the_text = "" 126 | for each in return_value: 127 | the_text += str(each) 128 | return_value = the_text 129 | 130 | if return_value == "": 131 | return_value = "No response " 132 | 133 | 134 | 135 | 136 | 137 | return return_value 138 | 139 | except Exception as e: 140 | traceback.print_exc() 141 | 142 | -------------------------------------------------------------------------------- /.github/workflows/deploys.yml: -------------------------------------------------------------------------------- 1 | name: Deploys 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_run: 6 | workflows: ["Release Generator"] 7 | types: 8 | - completed 9 | 10 | permissions: 11 | packages: write 12 | 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event }} 16 | cancel-in-progress: true 17 | jobs: 18 | 19 | tagext: 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | os: [ubuntu-latest] 24 | env: 25 | OS: ${{ matrix.os }} 26 | PYTHON: '3.8' 27 | steps: 28 | - uses: actions/checkout@v3 29 | with: 30 | fetch-depth: 0 31 | - name: Getting Tag 32 | id: tag_extractor 33 | run: echo "latest_tag=$(git describe --tags --abbrev=0)" >> "$GITHUB_OUTPUT" 34 | 35 | - name: Getting Tag 2 36 | id: tag_extractor_2 37 | run: | 38 | TAG=${{ steps.tag_extractor.outputs.latest_tag }} 39 | echo "latest_tag_2=${TAG:1} " >> "$GITHUB_OUTPUT" 40 | 41 | outputs: 42 | tag: ${{ steps.tag_extractor.outputs.latest_tag }} 43 | 44 | pypi: 45 | needs: tagext 46 | runs-on: ubuntu-latest 47 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 48 | environment: Deploys 49 | strategy: 50 | matrix: 51 | python-version: [3.8] 52 | 53 | steps: 54 | - uses: actions/checkout@v3 55 | with: 56 | fetch-depth: 0 57 | - name: Set up Python ${{ matrix.python-version }} 58 | uses: actions/setup-python@v2 59 | with: 60 | python-version: ${{ matrix.python-version }} 61 | 62 | 63 | - name: Build and Publish Python Packages 64 | env: 65 | TWINE_USERNAME: __token__ 66 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 67 | run: | 68 | python -m pip install --upgrade pip 69 | pip install setuptools wheel twine 70 | python setup.py sdist 71 | twine upload dist/* 72 | 73 | 74 | 75 | pypi_2: 76 | needs: tagext 77 | runs-on: ubuntu-latest 78 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 79 | environment: Deploys 80 | strategy: 81 | matrix: 82 | python-version: [3.8] 83 | 84 | steps: 85 | - uses: actions/checkout@v3 86 | with: 87 | fetch-depth: 0 88 | - name: Set up Python ${{ matrix.python-version }} 89 | uses: actions/setup-python@v2 90 | with: 91 | python-version: ${{ matrix.python-version }} 92 | 93 | 94 | - name: Build and Publish Python Packages 95 | env: 96 | TWINE_USERNAME: __token__ 97 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 98 | run: | 99 | python -m pip install --upgrade pip 100 | pip install setuptools wheel twine 101 | python gca_setup_generator.py 102 | python gca_setup.py sdist 103 | twine upload dist/* 104 | 105 | 106 | -------------------------------------------------------------------------------- /.github/issue-auto-comments.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | footer: | 3 | --- 4 | > I am a bot, and this is an automated message 🤖 5 | labels: 6 | - name: ✖️ Invalid 7 | labeled: 8 | issue: 9 | action: close 10 | body: > 11 | Hello @{{ issue.user.login }} your ticket has been marked as invalid. 12 | Please ensure you follow the issue template, provide all requested info, 13 | and be sure to check the docs + previous issues prior to raising tickets. 14 | pr: 15 | body: Thank you @{{ pull_request.user.login }} for suggesting this. Please follow the pull request templates. 16 | action: close 17 | 18 | - name: 👩‍💻 Good First Issue 19 | labeled: 20 | issue: 21 | body: > 22 | This issue has been marked as a good first issue for first-time contributors to implement! 23 | This is a great way to support the project, while also improving your skills, you'll also be credited as a contributor once your PR is merged. 24 | If you're new to web development, [here are a collection of resources](https://gpt-computer.khulnasoft.com/docs/developing#resources-for-beginners) 25 | to help you get started. You can also find step-by-step tutorials for common tasks within GPT Computer, on the [Dev Guides](https://gpt-computer.khulnasoft.com/docs/development-guides) page. 26 | If you need any support at all, feel free to reach out via [GitHub Discussions](https://github.com/khulnaSoft/GPT Computer/discussions). 27 | 28 | - name: ❌ wontfix 29 | labeled: 30 | issue: 31 | action: close 32 | body: > 33 | This ticked has been marked as 'wontfix', which usually means it is out-of-scope, or not feasible at this time. 34 | You can still fork the project and make the changes yourself, for support in doing so, please reference the [Developing Docs](https://gpt-computer.khulnasoft.com/docs/developing). 35 | 36 | - name: ✅ Fixed 37 | labeled: 38 | issue: 39 | body: > 40 | Hello @{{ issue.user.login }}! It looks like all or part of this issue has now been implemented :) 41 | If you're enjoying GPT Computer, please consider supporting the project- for ways to get involved, see [Contributing](https://gpt-computer.khulnasoft.com/docs/contributing) 💖 42 | 43 | - name: ‼️ High Priority 44 | labeled: 45 | issue: 46 | body: > 47 | This ticket has been marked as high priority, and has been bumped to the top of the priority list. 48 | You should expect an implementation to be pushed out within the next 7 days. Thank you for your patience. 49 | 50 | - name: 💀 Spam 51 | labeled: 52 | issue: 53 | action: close 54 | locking: lock 55 | lock_reason: spam 56 | body: > 57 | This issue has been identified as spam, and is now locked. 58 | Users who repeatedly raise spam issues may be blocked or reported. 59 | 60 | - name: ⛔ Don't Merge 61 | labeled: 62 | pr: 63 | body: This PR has been temporarily blocked from merging. 64 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/tts.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ..llm import * 3 | from ..utils.db import * 4 | from .tts_providers.openai import tts_openai 5 | from .tts_providers.microsoft_local import tts_microsoft_local 6 | except ImportError: 7 | from llm import * 8 | from utils.db import * 9 | from audio.tts_providers.openai import tts_openai 10 | from audio.tts_providers.microsoft_local import tts_microsoft_local 11 | 12 | import os 13 | import hashlib 14 | import random 15 | import threading 16 | 17 | 18 | def is_local_tts_available(): 19 | try: 20 | return True 21 | except: 22 | return False 23 | 24 | 25 | def is_openai_tts_available(): 26 | the_model = load_model_settings() 27 | if llm_settings[the_model]["provider"] == "openai": 28 | if load_api_key() != "CHANGE_ME": 29 | return True 30 | return False 31 | 32 | 33 | supported_openai_speakers = ["fable"] 34 | 35 | 36 | def random_model(exclude): 37 | models = supported_openai_speakers.copy() 38 | models.remove(exclude) 39 | return random.choice(models) 40 | 41 | 42 | def generate_speech_chunk(text_chunk, index, voice, results): 43 | sha = hashlib.sha256(text_chunk.encode()).hexdigest() 44 | location = os.path.join(artifacts_dir, f"{sha}.mp3") 45 | 46 | if os.path.exists(location): 47 | results[index] = location 48 | else: 49 | the_model = load_model_settings() 50 | tts_setting = load_tts_model_settings() 51 | if tts_setting == "openai": 52 | tts_openai(voice, text_chunk, location) 53 | 54 | if tts_setting == "microsoft_local": 55 | if not is_local_tts_available(): 56 | print( 57 | "Please install gpt-computer-agent[local_tts] to use local TTS" 58 | ) 59 | else: 60 | tts_microsoft_local(text_chunk, location) 61 | 62 | results[index] = location 63 | 64 | 65 | def split_text_to_sentences(text, max_chunk_size=300): 66 | """Splits text into sentences and ensures chunks do not exceed max_chunk_size.""" 67 | sentences = text.split(".") 68 | chunks = [] 69 | current_chunk = "" 70 | 71 | for sentence in sentences: 72 | sentence = sentence.strip() 73 | if len(current_chunk) + len(sentence) + 1 <= max_chunk_size: 74 | current_chunk += sentence + ". " 75 | else: 76 | chunks.append(current_chunk.strip()) 77 | current_chunk = sentence + ". " 78 | 79 | if current_chunk: 80 | chunks.append(current_chunk.strip()) 81 | 82 | return chunks 83 | 84 | 85 | def text_to_speech(text): 86 | text_chunks = split_text_to_sentences(text) 87 | 88 | threads = [] 89 | results = [None] * len(text_chunks) 90 | 91 | initial_voice = random.choice(supported_openai_speakers) 92 | 93 | for i, chunk in enumerate(text_chunks): 94 | voice = ( 95 | initial_voice if i % 2 == 0 else random_model(initial_voice) 96 | ) # Alternate voices 97 | thread = threading.Thread( 98 | target=generate_speech_chunk, args=(chunk, i, voice, results) 99 | ) 100 | threads.append(thread) 101 | thread.start() 102 | 103 | for thread in threads: 104 | thread.join() 105 | 106 | mp3_files = [result for result in results if result is not None] 107 | 108 | return mp3_files 109 | -------------------------------------------------------------------------------- /.github/workflows/release_generation.yml: -------------------------------------------------------------------------------- 1 | name: Release Generator 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Release"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | build_dmg: 15 | runs-on: macos-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up Python 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: '3.10' 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | - name: Build DMG 30 | run: | 31 | chmod +x build_scripts/openai/macos_build.sh 32 | ./build_scripts/openai/macos_build.sh 33 | - name: Upload artifact 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: gpt-computer-agent-openai-dmg 37 | path: dist/*.dmg 38 | 39 | build_exe: 40 | runs-on: windows-latest 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@v2 45 | 46 | - name: Set up Python 47 | uses: actions/setup-python@v4 48 | with: 49 | python-version: '3.11' 50 | cache: 'pip' # caching pip dependencies 51 | 52 | - name: Install dependencies 53 | run: | 54 | python -m pip install --upgrade pip 55 | - name: Build EXE 56 | run: | 57 | sh build_scripts/openai/windows_build.sh 58 | - name: Upload artifact 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: gpt-computer-agent-openai-exe 62 | path: dist/*.exe 63 | 64 | release: 65 | needs: [build_dmg, build_exe] 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v3 69 | with: 70 | fetch-depth: 0 71 | - name: Getting Tag 72 | id: tag_extractor 73 | run: echo "latest_tag=$(git describe --tags --abbrev=0)" >> "$GITHUB_OUTPUT" 74 | 75 | - uses: ncipollo/release-action@v1 76 | id: create_release 77 | with: 78 | name: GPT Computer Assistant ${{ steps.tag_extractor.outputs.latest_tag }} 79 | generateReleaseNotes: true 80 | tag: ${{ steps.tag_extractor.outputs.latest_tag }} 81 | 82 | - name: Download DMG Artifact 83 | uses: actions/download-artifact@v4.2.1 84 | with: 85 | name: gpt-computer-agent-openai-dmg 86 | path: dist_dmg 87 | 88 | - name: Download EXE Artifact 89 | uses: actions/download-artifact@v4.2.1 90 | with: 91 | name: gpt-computer-agent-openai-exe 92 | path: dist_exe 93 | 94 | - name: List DMG Directory 95 | run: ls -la dist_dmg 96 | 97 | - name: List EXE Directory 98 | run: ls -la dist_exe 99 | 100 | - name: Upload DMG Asset 101 | uses: actions/upload-release-asset@v1 102 | env: 103 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 104 | with: 105 | upload_url: ${{ steps.create_release.outputs.upload_url }} 106 | asset_path: dist_dmg/GPT_Computer_Agent.dmg 107 | asset_name: gpt-computer-agent-openai.dmg 108 | asset_content_type: application/octet-stream 109 | 110 | - name: Upload EXE Asset 111 | uses: actions/upload-release-asset@v1 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | with: 115 | upload_url: ${{ steps.create_release.outputs.upload_url }} 116 | asset_path: dist_exe/GPT_Computer_Agent.exe 117 | asset_name: gpt-computer-agent-openai.exe 118 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.mp3 4 | *.wav 5 | *.wav* 6 | gpt_computer_agent/utils/artifacts/*.png 7 | *.db 8 | *.db-shm 9 | *.db-wal 10 | test.py 11 | test_backup.py 12 | test_logo.png 13 | 14 | output/ 15 | dist/ 16 | 17 | # Byte-compiled / optimized / DLL files 18 | __pycache__/ 19 | *.py[cod] 20 | *$py.class 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | wheels/ 39 | share/python-wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | MANIFEST 44 | 45 | # PyInstaller 46 | # Usually these files are written by a python script from a template 47 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 48 | *.manifest 49 | *.spec 50 | 51 | # Installer logs 52 | pip-log.txt 53 | pip-delete-this-directory.txt 54 | 55 | # Unit test / coverage reports 56 | htmlcov/ 57 | .tox/ 58 | .nox/ 59 | .coverage 60 | .coverage.* 61 | .cache 62 | nosetests.xml 63 | coverage.xml 64 | *.cover 65 | *.py,cover 66 | .hypothesis/ 67 | .pytest_cache/ 68 | cover/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Django stuff: 75 | *.log 76 | local_settings.py 77 | db.sqlite3 78 | db.sqlite3-journal 79 | 80 | # Flask stuff: 81 | instance/ 82 | .webassets-cache 83 | 84 | # Scrapy stuff: 85 | .scrapy 86 | 87 | # Sphinx documentation 88 | docs/_build/ 89 | 90 | # PyBuilder 91 | .pybuilder/ 92 | target/ 93 | 94 | # Jupyter Notebook 95 | .ipynb_checkpoints 96 | 97 | # IPython 98 | profile_default/ 99 | ipython_config.py 100 | 101 | # pyenv 102 | # For a library or package, you might want to ignore these files since the code is 103 | # intended to run in multiple environments; otherwise, check them in: 104 | # .python-version 105 | 106 | # pipenv 107 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 108 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 109 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 110 | # install all needed dependencies. 111 | #Pipfile.lock 112 | 113 | # poetry 114 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 115 | # This is especially recommended for binary packages to ensure reproducibility, and is more 116 | # commonly ignored for libraries. 117 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 118 | #poetry.lock 119 | 120 | # pdm 121 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 122 | #pdm.lock 123 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 124 | # in version control. 125 | # https://pdm.fming.dev/#use-with-ide 126 | .pdm.toml 127 | 128 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 129 | __pypackages__/ 130 | 131 | # Celery stuff 132 | celerybeat-schedule 133 | celerybeat.pid 134 | 135 | # SageMath parsed files 136 | *.sage.py 137 | 138 | # Environments 139 | .env 140 | .venv 141 | env/ 142 | venv/ 143 | ENV/ 144 | env.bak/ 145 | venv.bak/ 146 | 147 | # Spyder project settings 148 | .spyderproject 149 | .spyproject 150 | 151 | # Rope project settings 152 | .ropeproject 153 | 154 | # mkdocs documentation 155 | /site 156 | 157 | # mypy 158 | .mypy_cache/ 159 | .dmypy.json 160 | dmypy.json 161 | 162 | # Pyre type checker 163 | .pyre/ 164 | 165 | # pytype static type analyzer 166 | .pytype/ 167 | 168 | # Cython debug symbols 169 | cython_debug/ 170 | 171 | # PyCharm 172 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 173 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 174 | # and can be added to the global gitignore or merged into this file. For a more nuclear 175 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 176 | .idea/ 177 | -------------------------------------------------------------------------------- /bump.py: -------------------------------------------------------------------------------- 1 | """Module for managing the version updates of a python package.""" 2 | 3 | import os 4 | import sys 5 | import re 6 | import logging 7 | import shlex 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def read_version(): 14 | """ 15 | Gets and extracts the version number from the '__init__.py' file of 16 | a Python package. 17 | 18 | Returns: 19 | str or None: The version number of the package if found, otherwise None. 20 | """ 21 | with open("gpt_computer_agent/__init__.py", "r") as file: 22 | for line in file: 23 | match = re.search(r"__version__ = '(.*)'", line) 24 | if match: 25 | return match.group(1) 26 | 27 | 28 | def increment_version(part, version): 29 | """ 30 | Simple function that increments the version number based on the given part 31 | i.e., ('major', 'minor', or 'patch'). 32 | 33 | Notes: 34 | Splits the version string into major, minor, and patch components, then 35 | increments the specified part by one 36 | 37 | Args: 38 | part (str): The part of the version number to increment 39 | ('major', 'minor', or 'patch'). 40 | version (str): The current version number in 'major.minor.patch' format. 41 | 42 | Returns: 43 | str: String containing new changes made to the version. 44 | """ 45 | major, minor, patch = map(int, version.split(".")) 46 | if part == "major": 47 | major += 1 48 | minor = 0 49 | patch = 0 50 | elif part == "minor": 51 | minor += 1 52 | patch = 0 53 | elif part == "patch": 54 | patch += 1 55 | return f"{major}.{minor}.{patch}" 56 | 57 | 58 | def write_version(version): 59 | """ 60 | Updates the `__version__` variable in the `__init__.py` file of the 61 | `gpt_computer_agent` package. 62 | 63 | Args: 64 | version (str): The new version number to replace the existing one. 65 | """ 66 | with open("gpt_computer_agent/__init__.py", "r+") as file: 67 | content = file.read() 68 | content = re.sub(r"__version__ = '.*'", f"__version__ = '{version}'", content) # fmt: off 69 | file.seek(0) 70 | file.write(content) 71 | 72 | 73 | def update_version(version): 74 | """ 75 | Updates the version number found in a list of files. 76 | 77 | Args: 78 | version (str): The new version number to replace the existing one. 79 | """ 80 | files = ["setup.py"] 81 | for file in files: 82 | with open(file, "r+") as f: 83 | content = f.read() 84 | content = re.sub(r' version=".*"', f' version="{version}"', content) # fmt: off 85 | f.seek(0) 86 | f.write(content) 87 | 88 | 89 | def create_tag(version): 90 | """ 91 | Uses the `os.system()` to create a `Git tag` for a specified version. 92 | 93 | Args: 94 | version (str): The version number for the git tag. 95 | """ 96 | os.system(f"git tag v{shlex.quote(version)}") 97 | 98 | 99 | def create_commit(version): 100 | """ 101 | Uses `os.system()` to add and commit the changed version number 102 | to the Git repository. 103 | 104 | Args: 105 | version (str): Version number included in the commit message. 106 | """ 107 | os.system("git add .") 108 | os.system(f"git commit -m 'Changed version number with v{shlex.quote(version)}'") 109 | 110 | 111 | def push(): 112 | """Pushes changes and tags to the repository.""" 113 | os.system("git push") 114 | os.system("git push --tag") 115 | 116 | 117 | def main(): 118 | """The main function for managing version updates.""" 119 | valid_parts = ["major", "minor", "patch"] 120 | if len(sys.argv) != 2 or sys.argv[1] not in valid_parts: 121 | logger.error(f"Usage: python version.py <{'|'.join(valid_parts)}>") 122 | sys.exit(1) 123 | 124 | part = sys.argv[1] 125 | version = read_version() 126 | new_version = increment_version(part, version) 127 | write_version(new_version) 128 | update_version(new_version) 129 | create_commit(new_version) 130 | create_tag(new_version) 131 | push() 132 | 133 | 134 | if __name__ == "__main__": 135 | main() 136 | -------------------------------------------------------------------------------- /.github/pr-badge.ym: -------------------------------------------------------------------------------- 1 | # Config file for pull-request-badge. See: https://pullrequestbadge.com/ by @stefanbuck 2 | # Dynamically inserts status badges into PR description, based on certain conditions 3 | 4 | # Show submitting user's username and profile link 5 | - label: 💕 Submitted by 6 | message: $payload.pull_request.user.login 7 | color: '#f73ae6' 8 | when: $payload.pull_request.author_association !== 'OWNER' 9 | url: 'https://github.com/$payload.pull_request.user.login' 10 | 11 | # Show a badge indicating the PR category, based on tag 12 | - label: Type 13 | message: ✨ Feature 14 | color: '#39b0fd' 15 | when: $labels.includes('✨ New Feature') 16 | - label: Type 17 | message: 🐛 Fix 18 | color: '#39b0fd' 19 | when: $labels.includes('🦋 Bug Fix') 20 | - label: Type 21 | message: 📕 Docs 22 | color: '#39b0fd' 23 | when: $labels.includes('📕 Docs') 24 | - label: Type 25 | message: 🛠️ Build Changes 26 | color: '#39b0fd' 27 | when: $labels.includes('🛠️ Build Changes') 28 | - label: Type 29 | message: 🛠️ Build Changes 30 | color: '#39b0fd' 31 | when: $labels.includes('🛠️ Build Changes') 32 | - label: Type 33 | message: 🚚 Refactor 34 | color: '#39b0fd' 35 | when: $labels.includes('🚚 Refactor') 36 | - label: Type 37 | message: 💄 Stylistic Changes 38 | color: '#39b0fd' 39 | when: $labels.includes('💄 Stylistic Changes') 40 | - label: Type 41 | message: 🌟 Showcase Addition 42 | color: '#39b0fd' 43 | when: $labels.includes('💯 Showcase') 44 | - label: Type 45 | message: 🏗️ Architecture 46 | color: '#39b0fd' 47 | when: $labels.includes('🏗️ Architectural Changes') 48 | - label: Type 49 | message: 🤖 Auto Submission 50 | color: '#39b0fd' 51 | when: $labels.includes('🤖 Auto') 52 | - label: Type 53 | message: 🌐 Language Update 54 | color: '#39b0fd' 55 | when: $labels.includes('🌐 Language') 56 | 57 | # Add size label based on very large or tiny PRs 58 | - label: PR Size 59 | message: Extra Large 60 | color: '#f9833e' 61 | when: '$additions > 1000' 62 | - label: PR Size 63 | message: Large 64 | color: '#f4b546' 65 | when: '$additions > 500 && $additions < 1000' 66 | - label: PR Size 67 | message: Medium 68 | color: '#f3ff59' 69 | when: '$additions > 10 && $additions < 500' 70 | - label: PR Size 71 | message: Quick 72 | color: '#3eef8b' 73 | when: '$additions < 10' 74 | 75 | # Show badge indicating PR status 76 | - label: Status 77 | message: ✏️ Draft 78 | when: $isDraft 79 | color: '#ffa933' 80 | - label: Status 81 | message: 🧱 Work in Progress 82 | when: $payload.pull_request.title.includes('WIP') 83 | color: '#29e3f4' 84 | - label: Status 85 | message: ✅ Ready 86 | color: '#3ef963' 87 | when: $labels.includes('🔀 Ready for Merge') 88 | 89 | # Show PR number, to destination and from destination 90 | - label: '#$prNumber' 91 | message: '$payload.pull_request.user.login /$payload.pull_request.head.ref → $payload.repository.full_name' 92 | color: '#ab5afc' 93 | url: 'https://github.com/$slug/tree/$branchName' 94 | 95 | # Show total code added minus deleted 96 | - label: New Code 97 | message: 'Commits: $payload.pull_request.commits | Files Changed: $payload.pull_request.changed_files | Additions: $payload.pull_request.additions-$payload.pull_request.deletions' 98 | color: '#dddd00' 99 | 100 | # Checks if the required sections are missing 101 | - label: ⚠️Missing 102 | message: Category 103 | color: '#f25265' 104 | when: $payload.pull_request.body.includes('Category') === false 105 | - label: ⚠️Missing 106 | message: Overview 107 | color: '#f25265' 108 | when: $payload.pull_request.body.includes('Overview') === false 109 | - label: ⚠️Missing 110 | message: Quality Checklist 111 | color: '#f25265' 112 | when: $payload.pull_request.body.includes('Code Quality Checklist') === false 113 | - label: ⚠️Description 114 | message: Incomplete 115 | color: '#f25265' 116 | when: $payload.pull_request.body.length < 25 117 | - label: ⚠️Missing 118 | message: Label 119 | color: '#f25265' 120 | when: $labels.length == 0 121 | 122 | # Show note when task list has unfinished items 123 | - label: ⚠️Notice 124 | message: Unchecked Tasks 125 | when: $payload.pull_request.body.includes('- [ ] ') 126 | color: '#f25265' 127 | 128 | # Show warning, when certain tags are applied 129 | - label: Warning 130 | message: ⛔ Do Not Merge 131 | color: '#f25265' 132 | when: $labels.includes("⛔ Don't Merge") 133 | - label: Warning 134 | message: 🚫 Merge Conflicts 135 | color: '#f25265' 136 | when: $labels.includes('🚫 Merge Conflicts') 137 | - label: Warning 138 | message: 🕸️ Inactive 139 | color: '#f25265' 140 | when: $labels.includes('🕸️ Inactive') 141 | - label: Warning 142 | message: 💀 Spam 143 | color: '#f25265' 144 | when: $labels.includes('💀 Spam') 145 | -------------------------------------------------------------------------------- /gpt_computer_agent/llm.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | from langchain_openai import ChatOpenAI, AzureChatOpenAI 3 | from langchain_community.chat_models import ChatOllama 4 | from langchain_google_genai import ChatGoogleGenerativeAI 5 | from langchain_groq import ChatGroq 6 | from langchain_anthropic import ChatAnthropic 7 | from langchain_aws import ChatBedrock 8 | 9 | 10 | try: 11 | from .utils.db import * 12 | from .custom_callback import customcallback 13 | from .llm_settings import llm_settings 14 | except ImportError: 15 | from utils.db import * 16 | from custom_callback import customcallback 17 | from llm_settings import llm_settings 18 | 19 | 20 | the_callback = customcallback(strip_tokens=False, answer_prefix_tokens=["Answer"]) 21 | 22 | 23 | def get_model(high_context=False, the_model=None): 24 | the_model = load_model_settings() if not the_model else the_model 25 | the_api_key = load_api_key() 26 | the_anthropic_api_key = load_anthropic_api_key() 27 | the_groq_api_key = load_groq_api_key() 28 | the_google_api_key = load_google_api_key() 29 | the_openai_url = load_openai_url() 30 | the_api_version = load_api_version() 31 | 32 | def open_ai_base(high_context): 33 | if the_openai_url == "default": 34 | true_model = the_model 35 | if high_context: 36 | true_model = "gpt-4-turbo" 37 | return { 38 | "model": true_model, 39 | "api_key": the_api_key, 40 | "max_retries": 15, 41 | "streaming": True, 42 | "callbacks": [the_callback], 43 | } 44 | else: 45 | return { 46 | "model": the_model, 47 | "api_key": the_api_key, 48 | "max_retries": 35, 49 | "streaming": True, 50 | "callbacks": [the_callback], 51 | } 52 | 53 | args_mapping = { 54 | ChatOpenAI: open_ai_base(high_context=high_context), 55 | ChatAnthropic: { 56 | "model": the_model, 57 | "api_key": the_anthropic_api_key, 58 | "max_retries": 35, 59 | "streaming": False, 60 | "callbacks": [the_callback], 61 | }, 62 | ChatBedrock: { 63 | "model_id": the_model, 64 | "aws_access_key_id": load_aws_access_key_id(), 65 | "aws_secret_access_key": load_aws_secret_access_key(), 66 | "region_name":"us-east-1", 67 | "streaming": False, 68 | "callbacks": [the_callback], 69 | }, 70 | AzureChatOpenAI: { 71 | "azure_deployment": the_model.replace("-azureopenai", ""), 72 | "api_version": the_api_version, 73 | "max_retries": 35, 74 | "streaming": True, 75 | "callbacks": [the_callback], 76 | }, 77 | ChatOllama: {"model": the_model}, 78 | ChatGroq: { 79 | "temperature": 0, 80 | "model_name": the_model.replace("-groq", ""), 81 | "groq_api_key": the_openai_url, 82 | }, 83 | ChatGoogleGenerativeAI: { 84 | "model": the_model, 85 | "google_api_key": the_google_api_key, 86 | }, 87 | } 88 | 89 | model_mapping = {} 90 | 91 | for model_name, model_args in llm_settings.items(): 92 | the_tuple = None 93 | if model_args["provider"] == "openai": 94 | the_tuple = (ChatOpenAI, args_mapping[ChatOpenAI]) 95 | elif model_args["provider"] == "anthropic": 96 | the_tuple = (ChatAnthropic, args_mapping[ChatAnthropic]) 97 | elif model_args["provider"] == "aws": 98 | the_tuple = (ChatBedrock, args_mapping[ChatBedrock]) 99 | elif model_args["provider"] == "azureai": 100 | import os 101 | os.environ["AZURE_OPENAI_API_KEY"] = the_api_key 102 | os.environ["AZURE_OPENAI_ENDPOINT"] = the_openai_url 103 | the_tuple = (AzureChatOpenAI, args_mapping[AzureChatOpenAI]) 104 | elif model_args["provider"] == "ollama": 105 | the_tuple = ( 106 | ChatOpenAI, 107 | { 108 | "api_key": "ollama", 109 | "base_url": "http://localhost:11434/v1", 110 | "model": model_name, 111 | }, 112 | ) 113 | elif model_args["provider"] == "google": 114 | the_tuple = (ChatGoogleGenerativeAI, args_mapping[ChatGoogleGenerativeAI]) 115 | elif model_args["provider"] == "groq": 116 | the_tuple = (ChatGroq, args_mapping[ChatGroq]) 117 | 118 | if the_tuple: 119 | model_mapping[model_name] = the_tuple 120 | 121 | model_class, args = model_mapping[the_model] 122 | return model_class(**args) if model_class else None 123 | 124 | 125 | def get_client(): 126 | the_api_key = load_api_key() 127 | return OpenAI(api_key=the_api_key) 128 | -------------------------------------------------------------------------------- /gpt_computer_agent/llm_settings.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .utils.db import * 3 | 4 | except ImportError: 5 | from utils.db import * 6 | 7 | llm_settings = { 8 | "gpt-4o": { 9 | "show_name": "gpt-4o (OpenAI)", 10 | "vision": True, 11 | "provider": "openai", 12 | "tools": True, 13 | "stream": True, 14 | }, 15 | "claude-3-5-sonnet-20241022": { 16 | "show_name": "claude-3-5-sonnet-20241022 (Anthropic)", 17 | "vision": True, 18 | "provider": "anthropic", 19 | "tools": True, 20 | "stream": False, 21 | }, 22 | "us.anthropic.claude-3-5-sonnet-20241022-v2:0": { 23 | "show_name": "claude-3-5-sonnet-20241022 (AWS)", 24 | "vision": True, 25 | "provider": "aws", 26 | "tools": True, 27 | "stream": False, 28 | }, 29 | "gpt-4o-azureopenai": { 30 | "show_name": "gpt-4o (AzureAI)", 31 | "vision": True, 32 | "provider": "azureai", 33 | "tools": True, 34 | "stream": True, 35 | }, 36 | "gpt-4o-mini": { 37 | "show_name": "gpt-4o-mini (OpenAI)", 38 | "vision": True, 39 | "provider": "openai", 40 | "tools": True, 41 | "stream": True, 42 | }, 43 | "gpt-4-turbo": { 44 | "show_name": "gpt-4-turbo (OpenAI)", 45 | "vision": False, 46 | "provider": "openai", 47 | "tools": True, 48 | "stream": True, 49 | }, 50 | "gpt-3.5": { 51 | "show_name": "gpt-3.5 (OpenAI)", 52 | "vision": False, 53 | "provider": "openai", 54 | "tools": True, 55 | "stream": True, 56 | }, 57 | "gpt-3.5-turbo": { 58 | "show_name": "gpt-3.5-turbo (OpenAI)", 59 | "vision": False, 60 | "provider": "openai", 61 | "tools": True, 62 | "stream": True, 63 | }, 64 | "llama3": { 65 | "show_name": "Llama3 (Ollama)", 66 | "vision": False, 67 | "provider": "ollama", 68 | "tools": False, 69 | "stream": False, 70 | }, 71 | "llama3.1": { 72 | "show_name": "Llama3.1 (Ollama)", 73 | "vision": False, 74 | "provider": "ollama", 75 | "tools": True, 76 | "stream": False, 77 | }, 78 | "qwen2:1.5b": { 79 | "show_name": "Qwen2 1.5b (Ollama)", 80 | "vision": False, 81 | "provider": "ollama", 82 | "tools": False, 83 | "stream": False, 84 | }, 85 | "llava": { 86 | "show_name": "Llava (Ollama)", 87 | "vision": True, 88 | "provider": "ollama", 89 | "tools": False, 90 | "stream": False, 91 | }, 92 | "bakllava": { 93 | "show_name": "BakLLaVA (Ollama)", 94 | "vision": True, 95 | "provider": "ollama", 96 | "tools": False, 97 | "stream": False, 98 | }, 99 | "llava-llama3": { 100 | "show_name": "Llava-Llama3 (Ollama)", 101 | "vision": True, 102 | "provider": "ollama", 103 | "tools": False, 104 | "stream": False, 105 | }, 106 | "llava-phi3": { 107 | "show_name": "LLaVA-Phi-3 (Ollama)", 108 | "vision": True, 109 | "provider": "ollama", 110 | "tools": False, 111 | "stream": False, 112 | }, 113 | "gemini-pro": { 114 | "show_name": "gemini-pro (Google)", 115 | "vision": True, 116 | "provider": "google", 117 | "tools": True, 118 | "stream": True, 119 | }, 120 | "mixtral-8x7b-groq": { 121 | "show_name": "Mixtral 8x7b (Groq)", 122 | "vision": False, 123 | "provider": "groq", 124 | "tools": True, 125 | "stream": True, 126 | }, 127 | } 128 | 129 | 130 | def get_openai_models(): 131 | return [k for k, v in llm_settings.items() if v["provider"] == "openai"] 132 | 133 | def get_azureai_models(): 134 | return [k for k, v in llm_settings.items() if v["provider"] == "azureai"] 135 | 136 | 137 | def get_ollama_models(): 138 | return [k for k, v in llm_settings.items() if v["provider"] == "ollama"] 139 | 140 | 141 | def get_google_models(): 142 | return [k for k, v in llm_settings.items() if v["provider"] == "google"] 143 | 144 | 145 | def get_groq_models(): 146 | return [k for k, v in llm_settings.items() if v["provider"] == "groq"] 147 | 148 | 149 | llm_show_name_ = {} 150 | for k, v in llm_settings.items(): 151 | llm_show_name_[v["show_name"]] = k 152 | 153 | llm_show_name = llm_show_name_ 154 | 155 | 156 | def first_message(): 157 | from .character import name, developer, get_website_content 158 | from .cu.computer import width, height, display_num 159 | from .utils.db import load_system_prompt 160 | model = load_model_settings() 161 | 162 | the_text = f""" 163 | {load_system_prompt()} 164 | """ 165 | 166 | the_website_content = get_website_content() 167 | if the_website_content: 168 | the_text += f""" 169 | # The Website Content of the User 170 | 171 | {the_website_content} 172 | 173 | """ 174 | 175 | return the_text 176 | 177 | 178 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | info@khulnasoft.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | <p align="center"> 2 | <a href="#"> 3 | <img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/176c8ddb-219e-444e-8782-1f8c37a92678" alt="Logo" width="250" > 4 | </a> 5 | 6 | <h3 align="center">GPT 计算机助手</h3> 7 | 8 | <p align="center"> 9 | 适用于 Windows、MacOS 和 Ubuntu 的 gpt-4o 10 | <br /> 11 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"><strong>文档</strong></a> 12 | . 13 | <a href="https://github.com/khulnasoft/gpt-computer-agent/#Capabilities"><strong>探索功能 »</strong></a> 14 | <br /> 15 | </p> 16 | <br> 17 | <p align="center"> 18 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"> 19 | <img src="https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white" alt="windows"> 20 | </a> 21 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"> 22 | <img src="https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=apple&logoColor=white" alt="macos"> 23 | </a> 24 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"> 25 | <img src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black" alt="linux"> 26 | </a> 27 | <br> 28 | 29 | </p> 30 | <p align="center"> 31 | <a href="https://www.python.org/"> 32 | <img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" alt="Made_with_python"> 33 | </a> 34 | . 35 | <img src="https://static.pepy.tech/personalized-badge/gpt-computer-agent?period=total&units=international_system&left_color=grey&right_color=blue&left_text=PyPI%20Downloads" alt="pypi_downloads"> 36 | </p> 37 | 38 | 39 | <p align="center"> 40 | <a href="https://discord.gg/qApFmWMt8x"><img alt="Static Badge" src="https://img.shields.io/badge/Discord-Join?style=social&logo=discord" width=150></a> 41 | <a href="https://x.com/GPTCompAsst"><img alt="Static Badge" src="https://img.shields.io/badge/X-Join?style=social&logo=x" width=100></a> 42 | 43 | </p> 44 | 45 | |[ENGLISH](README.md)|简体中文|[正體中文](README.zh_TW.md)|[TÜRKÇE](README.TR.md) 46 | 47 | # GPT 计算机助手 48 | 你好,这是一个将 ChatGPT MacOS 应用程序提供给 Windows 和 Linux 的替代工作。因此,这是一个全新且稳定的项目。此时,您可以轻松地将其作为 Python 库安装,但我们将准备一个流水线来提供本机安装脚本 (.exe)。 49 | 50 | 由 <a href="https://github.com/KhulnaSoft/Tiger"><strong>KhulnaSoft Tiger 🐅</strong></a> 提供支持的功能集成中心。 51 | 52 | ## 安装 && 运行 53 | 需要 >= Python 3.9 54 | ```console 55 | pip3 install 'gpt-computer-agent[default]' 56 | ``` 57 | 58 | ```console 59 | computeragent 60 | ``` 61 | 62 | 63 | 64 | ### 演示视频(1 分钟) 65 | 66 | https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/26ae3624-e619-44d6-9b04-f39cf1ac1f8f 67 | 68 | 69 | 70 | ## 使用案例 71 | 72 | <table> 73 | <tr> 74 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/b4a4f11e-5588-4656-b5d7-b612a9a2855b" alt="Take Meeting Notes" width="500"/></td> 75 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/49eeac70-b33a-4ec4-8125-64127621ed62" alt="Daily Assistant" width="500"/></td> 76 | </tr> 77 | <tr> 78 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/10b69a18-033c-4d81-8ac9-f4e3c65b59c3" alt="Read Docs" width="500"/></td> 79 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/0f483bae-ffaf-4311-8653-c0dc64fb5ebe" alt="Coding Assistant" width="500"/></td> 80 | 81 | </tr> 82 | </table> 83 | 84 | 85 | 86 | ## 路线图 87 | 88 | | 功能 | 状态 | 目标发布 | 89 | |---------------------------------|--------------|--------------| 90 | | 清除聊天记录 | 已完成 | 2024 年第二季度| 91 | | 长音频支持(拆分 20mb) | 已完成 | 2024 年第二季度| 92 | | 文本输入 | 已完成 | 2024 年第二季度| 93 | | 仅文本模式(静音) | 已完成 | 2024 年第二季度| 94 | | 添加配置文件(不同聊天) | 已完成 | 2024 年第二季度| 95 | | 更多关于助手状态的反馈 | 已完成 | 2024 年第二季度| 96 | | **新 UI** | 计划中 | 2024 年第二季度| 97 | | **我们的自定义代理基础设施** | 计划中 | 2024 年第二季度| 98 | | **本机应用程序,exe,dmg,appimage** | 计划中 | 2024 年第二季度| 99 | | **DeepFace 集成(面部识别)** | 计划中 | 2024 年第二季度| 100 | | **本地模式(使用 Ollama,语音和视觉模型)** | 计划中 | 2024 年第二季度| 101 | 102 | 103 | #### 代理基础设施 | 即将推出 104 | 105 | ```python 106 | from gpt-comptuer-assistant import crew, agent 107 | 108 | coder = agent("你是一名高级 Python 开发者") 109 | 110 | manager = agent("你是一名高级项目经理") 111 | 112 | assistant = crew( 113 | [coder, manager] 114 | ) 115 | 116 | assistant.gui() 117 | ``` 118 | 119 | 120 | 121 | 122 | ## 功能 123 | 此时我们拥有许多基础设施元素。我们只是希望提供 ChatGPT 应用中已经存在的所有功能。 124 | 125 | | 功能 | 描述 | 126 | |-----------------------------------|-------------------------------| 127 | | **屏幕读取** | OK | 128 | | **麦克风** | OK | 129 | | **系统音频** | OK | 130 | | **内存** | OK | 131 | | **打开和关闭应用程序** | OK | 132 | | **打开一个 URL** | OK | 133 | | **剪贴板** | OK | 134 | | **搜索引擎** | OK | 135 | | **编写和运行 Python** | OK | 136 | | **编写和运行 SH** | OK | 137 | | **使用你的 Telegram 账户** | OK | 138 | | **知识管理** | OK | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | ## 用法 147 | 148 | ![options](https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/20972b1e-6d4f-4314-8470-f2fcf79b6e6d) 149 | 150 | 151 | 152 | ** 第一次单击包含麦克风或系统音频的选项后,需要再次单击相同选项以停止。 153 | 154 | 155 | 156 | ## 贡献者 157 | 158 | <a href="https://github.com/khulnasoft/gpt-computer-agent/graphs/contributors"> 159 | <img src="https://contrib.rocks/image?repo=khulnasoft/gpt-computer-agent" /> 160 | </a> -------------------------------------------------------------------------------- /gpt_computer_agent/gui/button.py: -------------------------------------------------------------------------------- 1 | import pyautogui 2 | from .signal import * 3 | import threading 4 | 5 | try: 6 | 7 | from ..screen.shot import * 8 | from ..agent.process import * 9 | from ..agent.chat_history import clear_chat_history 10 | from ..utils.db import ( 11 | screenshot_path, 12 | save_api_key, 13 | load_api_key, 14 | activate_just_text_model, 15 | deactivate_just_text_model, 16 | is_just_text_model_active, 17 | set_profile, 18 | get_profile, 19 | ) 20 | from ..screen.shot import take_screenshot 21 | except ImportError: 22 | 23 | from screen.shot import * 24 | from agent.process import * 25 | from utils.db import ( 26 | screenshot_path, 27 | ) 28 | from screen.shot import take_screenshot 29 | 30 | recording_thread = None 31 | 32 | 33 | class ButtonHandler: 34 | """Handles button click events and corresponding actions.""" 35 | 36 | def __init__(self, main_window): 37 | """Initialize the ButtonHandler.""" 38 | self.recording = False 39 | self.main_window = main_window 40 | self.process_audio_thread = None 41 | 42 | signal_handler.recording_started.connect(self.on_recording_started) 43 | signal_handler.recording_stopped.connect(self.on_recording_stopped) 44 | signal_handler.assistant_thinking.connect(self.on_assistant_thinking) 45 | signal_handler.assistant_response_ready.connect( 46 | self.on_assistant_response_ready 47 | ) 48 | signal_handler.assistant_response_stopped.connect( 49 | self.on_assistant_response_stopped 50 | ) 51 | 52 | def toggle_recording( 53 | self, 54 | no_screenshot=False, 55 | take_system_audio=False, 56 | dont_save_image=False, 57 | new_record=False, 58 | ): 59 | """Toggle audio recording.""" 60 | 61 | try: 62 | from ..audio.record import stop_recording, start_recording 63 | except ImportError: 64 | from audio.record import stop_recording, start_recording 65 | 66 | if self.recording and not new_record: 67 | stop_recording() 68 | self.recording = False 69 | else: 70 | if not no_screenshot: 71 | screenshot = pyautogui.screenshot() 72 | screenshot.save(screenshot_path) 73 | 74 | self.no_screenshot = no_screenshot 75 | self.take_system_audio = take_system_audio 76 | self.dont_save_image = dont_save_image 77 | 78 | global recording_thread 79 | if ( 80 | recording_thread is None 81 | or not recording_thread.is_alive() 82 | or new_record 83 | ): 84 | recording_thread = threading.Thread( 85 | target=start_recording, 86 | args=( 87 | take_system_audio, 88 | self, 89 | ), 90 | ) 91 | recording_thread.start() 92 | signal_handler.recording_started.emit() 93 | 94 | def on_recording_started(self): 95 | """Handle event when recording starts.""" 96 | 97 | self.recording = True 98 | self.main_window.update_state("talking") 99 | 100 | def on_recording_stopped(self): 101 | """Handle event when recording stops.""" 102 | 103 | print("ON RECORDING STOPPED") 104 | self.recording = False 105 | self.main_window.update_state("thinking") 106 | if ( 107 | self.process_audio_thread is None 108 | or not self.process_audio_thread.is_alive() 109 | ): 110 | signal_handler.assistant_thinking.emit() 111 | self.process_audio_thread = threading.Thread( 112 | target=process_audio, 113 | args=( 114 | not self.no_screenshot, 115 | self.take_system_audio, 116 | self.dont_save_image, 117 | ), 118 | ) 119 | self.process_audio_thread.start() 120 | 121 | def just_screenshot(self): 122 | """Take a screenshot.""" 123 | 124 | take_screenshot() 125 | self.process_audio_thread = threading.Thread(target=process_screenshot) 126 | self.process_audio_thread.start() 127 | 128 | def on_assistant_response_stopped(self): 129 | """Handle event when assistant's response stops.""" 130 | 131 | self.main_window.update_state("idle") 132 | 133 | def on_assistant_thinking(self): 134 | """Handle event when assistant is thinking.""" 135 | 136 | self.main_window.update_state("thinking") 137 | 138 | def on_assistant_response_ready(self): 139 | """Handle event when assistant's response is ready.""" 140 | 141 | self.main_window.update_state("aitalking") 142 | 143 | def input_text(self, text): 144 | """Handle input text.""" 145 | 146 | self.main_window.update_state("thinking") 147 | if ( 148 | self.process_audio_thread is None 149 | or not self.process_audio_thread.is_alive() 150 | ): 151 | signal_handler.assistant_thinking.emit() 152 | self.process_audio_thread = threading.Thread( 153 | target=process_text, args=(text,) 154 | ) 155 | self.process_audio_thread.start() 156 | 157 | def input_text_screenshot(self, text): 158 | """Handle input text with screenshot.""" 159 | 160 | screenshot = pyautogui.screenshot() 161 | screenshot.save(screenshot_path) 162 | 163 | self.main_window.update_state("thinking") 164 | if ( 165 | self.process_audio_thread is None 166 | or not self.process_audio_thread.is_alive() 167 | ): 168 | signal_handler.assistant_thinking.emit() 169 | self.process_audio_thread = threading.Thread( 170 | target=process_text, 171 | args=(text,), 172 | kwargs={"screenshot_path": screenshot_path}, 173 | ) 174 | self.process_audio_thread.start() 175 | -------------------------------------------------------------------------------- /gpt_computer_agent/audio/record.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ..gui.signal import * 3 | from ..utils.db import * 4 | from ..utils.telemetry import my_tracer, os_name 5 | from ..gpt_computer_agent import the_input_box 6 | except ImportError: 7 | from gui.signal import * 8 | from utils.db import * 9 | from utils.telemetry import my_tracer, os_name 10 | from gpt_computer_agent import the_input_box 11 | import numpy as np 12 | import sounddevice as sd 13 | import soundfile as sf 14 | import scipy.io.wavfile as wavfile 15 | import soundcard as sc 16 | import threading 17 | import time 18 | from scipy.io.wavfile import write 19 | 20 | samplerate = 48000 # Updated samplerate for better quality 21 | channels = 1 22 | recording = False 23 | 24 | audio_data = None 25 | 26 | user_id = load_user_id() 27 | os_name_ = os_name() 28 | 29 | 30 | 31 | 32 | import queue 33 | 34 | # Initialize a queue to keep the last N audio levels (rolling window) 35 | audio_levels = queue.Queue(maxsize=10) # Adjust size as needed 36 | 37 | 38 | def calculate_dynamic_threshold(): 39 | """Calculate a dynamic threshold based on recent audio levels.""" 40 | if audio_levels.qsize() == 0: 41 | return 0.01 # Default threshold if no data is available 42 | else: 43 | # Calculate the average of the last N audio levels 44 | return np.mean(list(audio_levels.queue)) * 2 # Adjust multiplier as needed 45 | 46 | 47 | silence_start_time = None 48 | 49 | auto_stop_recording = True 50 | 51 | 52 | def start_recording(take_system_audio, buttonhandler): 53 | """Start recording audio from microphone and/or system sound.""" 54 | with my_tracer.start_span("start_recording") as span: 55 | span.set_attribute("user_id", user_id) 56 | span.set_attribute("os_name", os_name_) 57 | 58 | global the_input_box_pre 59 | from ..gpt_computer_agent import the_input_box, the_main_window 60 | 61 | the_input_box_pre = the_input_box.toPlainText() 62 | 63 | the_main_window.update_from_thread("Click again when recording is done") 64 | global recording, audio_data, silence_start_time, auto_stop_recording 65 | recording = True 66 | audio_data = np.array([], dtype="float32") 67 | print("Recording started...") 68 | 69 | threshold = 0.01 # Define the threshold for stopping the recording 70 | silence_duration = ( 71 | 2 # Duration in seconds to consider as silence before stopping 72 | ) 73 | silence_start_time = None 74 | recording_start_time = time.time() # Record the start time of the recording 75 | 76 | auto_stop_recording = is_auto_stop_recording_setting_active() 77 | 78 | def callback(indata, frames, time_info, status): 79 | global audio_data, recording, silence_start_time, auto_stop_recording 80 | current_level = np.max(np.abs(indata)) 81 | 82 | # Add the current level to the queue 83 | if audio_levels.full(): 84 | audio_levels.get() # Remove the oldest level if the queue is full 85 | audio_levels.put(current_level) 86 | 87 | # Calculate dynamic threshold based on recent audio levels 88 | dynamic_threshold = calculate_dynamic_threshold() 89 | 90 | if recording: 91 | audio_data = np.append(audio_data, indata) 92 | # Check if the audio is below the dynamic threshold 93 | if current_level < dynamic_threshold and auto_stop_recording: 94 | if silence_start_time is None: 95 | silence_start_time = time.time() # Mark the start of silence 96 | 97 | # Ensure recording has been ongoing for at least 3 seconds before considering auto-stop 98 | elif (time.time() - silence_start_time) > silence_duration and ( 99 | time.time() - recording_start_time 100 | ) > 3: 101 | recording = False 102 | buttonhandler.recording = False 103 | 104 | else: 105 | silence_start_time = None 106 | 107 | def record_audio(): 108 | with my_tracer.start_span("record_audio") as span: 109 | span.set_attribute("user_id", user_id) 110 | span.set_attribute("os_name", os_name_) 111 | global recording 112 | mics = sc.all_microphones(include_loopback=True) 113 | default_mic = mics[0] 114 | data = [] 115 | with default_mic.recorder(samplerate=148000) as mic: 116 | print("Recording...") 117 | while recording: 118 | frame = mic.record(numframes=4096) 119 | data.append(frame) 120 | data = np.concatenate(data, axis=0) 121 | data_int16 = (data * 32767).astype("int16") 122 | wavfile.write(system_sound_location, 148000, data_int16) 123 | 124 | if take_system_audio: 125 | recording_thread = threading.Thread(target=record_audio) 126 | recording_thread.start() 127 | 128 | with sd.InputStream(callback=callback, channels=channels, samplerate=samplerate): 129 | while recording: 130 | sd.sleep(100) 131 | 132 | if not recording: 133 | sf.write(mic_record_location, audio_data, samplerate) 134 | print("Audio saved as voice_input.wav") 135 | signal_handler.recording_stopped.emit() 136 | 137 | 138 | def stop_recording(): 139 | """Stop recording audio.""" 140 | global recording 141 | recording = False 142 | print("Recording stopped") 143 | 144 | 145 | def quick_speech_to_text(time_total: int = 5) -> str: 146 | global samplerate, channels, samplerate 147 | 148 | quic_location = "temp.wav" 149 | 150 | myrecording = sd.rec( 151 | int(time_total * samplerate), samplerate=samplerate, channels=channels 152 | ) 153 | sd.wait() # Wait until recording is finished 154 | write(quic_location, samplerate, myrecording) # Save as WAV file 155 | 156 | try: 157 | from .stt import speech_to_text 158 | except ImportError: 159 | from stt import speech_to_text 160 | 161 | return speech_to_text(quic_location) 162 | -------------------------------------------------------------------------------- /gpt_computer_agent/start.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | import webbrowser 5 | 6 | 7 | 8 | 9 | def start_api(): 10 | try: 11 | from .api import start_api 12 | 13 | start_api(api=True) 14 | except: 15 | raise Exception( 16 | "API could not be started, please install gpt-computer-agent[api]" 17 | ) 18 | 19 | 20 | 21 | 22 | 23 | def start(api=False): 24 | if api: 25 | return start_api() 26 | from PyQt5.QtWidgets import QApplication 27 | from PyQt5.QtGui import QIcon 28 | from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction 29 | from PyQt5.QtCore import Qt 30 | from pynput import keyboard 31 | """ 32 | Starts the computer assistant application. 33 | 34 | This function starts the computer assistant application, which includes parsing command-line arguments 35 | to set the profile, initializing the graphical user interface, and starting the application event loop. 36 | 37 | Command-line Arguments: 38 | --profile (str): The profile to use for the application. 39 | 40 | Raises: 41 | ImportError: If the required modules or packages are not found. 42 | 43 | Returns: 44 | None 45 | """ 46 | 47 | try: 48 | pass 49 | except: 50 | pass 51 | 52 | # get --profile argument with library 53 | import argparse 54 | 55 | parser = argparse.ArgumentParser() 56 | parser.add_argument("--profile", help="profile to use") 57 | parser.add_argument("--api", help="Enable API mode", action="store_true") 58 | 59 | parser.add_argument("--set_tts_provider", help="Set tts provider only") 60 | parser.add_argument("--set_stt_provider", help="Set stt provider only") 61 | 62 | parser.add_argument("--set_llm", help="Set llm model only") 63 | 64 | args = parser.parse_args() 65 | 66 | set_tts_provider = args.set_tts_provider 67 | 68 | if set_tts_provider is not None: 69 | from .utils.db import save_tts_model_settings 70 | 71 | save_tts_model_settings(set_tts_provider) 72 | return 73 | 74 | set_stt_provider = args.set_stt_provider 75 | 76 | if set_stt_provider is not None: 77 | from .utils.db import save_stt_model_settings 78 | 79 | save_stt_model_settings(set_stt_provider) 80 | return 81 | 82 | set_llm = args.set_llm 83 | 84 | if set_llm is not None: 85 | from .utils.db import save_model_settings 86 | 87 | save_model_settings(set_llm) 88 | return 89 | 90 | profile = args.profile 91 | 92 | api_arg = args.api 93 | print("Profile:", profile) 94 | 95 | if profile is not None: 96 | from .utils.db import set_profile 97 | 98 | set_profile(profile) 99 | 100 | try: 101 | from .utils.db import ( 102 | load_tts_model_settings, 103 | load_stt_model_settings, 104 | is_logo_active_setting_active, 105 | load_logo_file_path, 106 | ) 107 | except ImportError: 108 | from utils.db import ( 109 | load_tts_model_settings, 110 | load_stt_model_settings, 111 | load_logo_file_path, 112 | ) 113 | 114 | if load_tts_model_settings() != "openai": 115 | from .audio.tts_providers.microsoft_local import preload_tts_microsoft_local 116 | 117 | preload_tts_microsoft_local() 118 | 119 | if load_stt_model_settings() != "openai": 120 | from .audio.stt_providers.openai_whisper_local import ( 121 | preload_stt_openai_whisper_local, 122 | ) 123 | 124 | preload_stt_openai_whisper_local() 125 | 126 | try: 127 | from .gpt_computer_agent import MainWindow 128 | except ImportError: 129 | from gpt_computer_agent import MainWindow 130 | os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" 131 | 132 | if api or api_arg: 133 | print("API Enabled") 134 | MainWindow.api_enabled = True 135 | 136 | app = QApplication(sys.argv) 137 | ex = MainWindow() 138 | from PyQt5 import QtGui 139 | from PyQt5 import QtCore 140 | 141 | app_icon = QtGui.QIcon() 142 | 143 | app_icon.addFile(load_logo_file_path(), QtCore.QSize(48, 48)) 144 | app.setWindowIcon(app_icon) 145 | 146 | ex.the_app = app 147 | 148 | # Create the tray 149 | menu_icon = QtGui.QIcon() 150 | menu_icon.addFile(load_logo_file_path(), QtCore.QSize(48, 48)) 151 | 152 | menu_active_icon = QtGui.QIcon() 153 | menu_active_icon.addFile(load_logo_file_path(), QtCore.QSize(48, 48)) 154 | 155 | tray = QSystemTrayIcon() 156 | tray.setIcon(menu_icon) 157 | tray.setVisible(True) 158 | ex.tray = tray 159 | ex.tray_active_icon = menu_active_icon 160 | ex.tray_icon = menu_icon 161 | 162 | # Create the menu 163 | menu = QMenu() 164 | 165 | ex.the_tray = tray 166 | 167 | show_menu = QAction("Show") 168 | 169 | def show_menu_connect(): 170 | ex.setWindowState(Qt.WindowNoState) 171 | 172 | show_menu.triggered.connect(show_menu_connect) 173 | menu.addAction(show_menu) 174 | 175 | hide_menu = QAction("Hide") 176 | hide_menu.triggered.connect(ex.showMinimized) 177 | menu.addAction(hide_menu) 178 | 179 | menu.addSeparator() 180 | 181 | if platform.system() == "Darwin": 182 | the_text_of_screenshot_and_microphone = ( 183 | "Action: ⌃+⌥+⌘+up Screenshot and Microphone" 184 | ) 185 | else: 186 | the_text_of_screenshot_and_microphone = ( 187 | "Action: ctrl+alt+windows+up Screenshot and Microphone" 188 | ) 189 | screenshot_and_microphone = QAction(the_text_of_screenshot_and_microphone) 190 | 191 | def screenshot_and_microphone_connect(): 192 | ex.setWindowState(Qt.WindowNoState) 193 | ex.screenshot_and_microphone_button_action() 194 | 195 | screenshot_listener = keyboard.GlobalHotKeys( 196 | {"<ctrl>+<alt>+<cmd>+<up>": screenshot_and_microphone_connect} 197 | ) 198 | screenshot_listener.start() 199 | 200 | screenshot_and_microphone.triggered.connect(screenshot_and_microphone_connect) 201 | menu.addAction(screenshot_and_microphone) 202 | 203 | menu.addSeparator() 204 | 205 | action = QAction("Open GitHub Issues") 206 | action.triggered.connect( 207 | lambda: webbrowser.open( 208 | "https://github.com/khulnasoft/gpt-computer-agent/issues" 209 | ) 210 | ) 211 | menu.addAction(action) 212 | 213 | # Add a Quit option to the menu. 214 | quit = QAction("Quit") 215 | quit.triggered.connect(app.quit) 216 | menu.addAction(quit) 217 | 218 | # Add the menu to the tray 219 | tray.setContextMenu(menu) 220 | 221 | sys.exit(app.exec_()) 222 | -------------------------------------------------------------------------------- /gpt_computer_agent/display_tools.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from langchain.tools import tool 4 | import traceback 5 | 6 | import pyautogui 7 | import time 8 | 9 | try: 10 | from .utils.db import * 11 | from .llm import get_model 12 | from .top_bar_wrapper import wrapper 13 | from .llm_settings import llm_settings 14 | 15 | except ImportError: 16 | from utils.db import * 17 | from top_bar_wrapper import wrapper 18 | from llm_settings import llm_settings 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | def mouse_scroll_(direction: str, amount: int = 1) -> bool: 32 | """ 33 | A function to scroll the mouse wheel. 34 | 35 | Parameters: 36 | - direction (str): The direction of the scroll. Possible values are "up" and "down". 37 | - amount (int): The amount of scrolling to be performed. The default value is 1. 38 | 39 | Returns: 40 | - bool: True if the scrolling was performed successfully, False otherwise. 41 | """ 42 | try: 43 | import pyautogui 44 | 45 | pyautogui.FAILSAFE = False 46 | 47 | if direction == "up": 48 | pyautogui.scroll(amount) 49 | elif direction == "down": 50 | pyautogui.scroll(-amount) 51 | return True 52 | except: 53 | traceback.print_exc() 54 | return False 55 | 56 | 57 | mouse_scroll = tool(mouse_scroll_) 58 | 59 | 60 | 61 | def extract_code_from_result(llm_output): 62 | """ 63 | Extract the Python code from the LLM output. 64 | """ 65 | code_match = re.search(r'```json\n(.*?)```', llm_output, re.DOTALL) 66 | if code_match: 67 | return code_match.group(1).strip() 68 | return llm_output.strip() 69 | 70 | 71 | def click_to_text_(text:str, double_click=False) -> bool: 72 | """ 73 | Click on the text 74 | 75 | """ 76 | 77 | try: 78 | from .cu.ask_anthropic import ask_anthropic 79 | from .cu.computer import click_action, mouse_move_action 80 | except ImportError: 81 | from cu.ask_anthropic import ask_anthropic 82 | from cu.computer import click_action, mouse_move_action 83 | 84 | print("click_to_text") 85 | print("text", text) 86 | x_y = ask_anthropic(f"dont use tools, give me exactly location of '{text}' text as json x,y like"+ """{'x': 0, 'y': 0}"""+". Only return the json with ```json ```") 87 | print("result", x_y) 88 | 89 | x_y = extract_code_from_result(x_y) 90 | 91 | x_y = json.loads(x_y) 92 | 93 | 94 | pyautogui.click(x_y['x'], x_y['y'], button='left') 95 | if double_click: 96 | time.sleep(0.1) 97 | pyautogui.click(x_y['x'], x_y['y'], button='left') 98 | 99 | return True 100 | 101 | 102 | click_to_text = tool(click_to_text_) 103 | 104 | 105 | 106 | def click_to_icon_(icon:str, double_click=False) -> bool: 107 | """ 108 | Click on the icon 109 | 110 | """ 111 | 112 | try: 113 | from .cu.ask_anthropic import ask_anthropic 114 | from .cu.computer import click_action, mouse_move_action 115 | except ImportError: 116 | from cu.ask_anthropic import ask_anthropic 117 | from cu.computer import click_action, mouse_move_action 118 | 119 | print("click_to_icon") 120 | print("icon", icon) 121 | x_y = ask_anthropic(f"dont use tools, give me exactly location of '{icon}' icon as json x,y like"+ """{'x': 0, 'y': 0}"""+". Only return the json with ```json ```") 122 | print("result", x_y) 123 | 124 | x_y = extract_code_from_result(x_y) 125 | 126 | x_y = json.loads(x_y) 127 | 128 | pyautogui.click(x_y['x'], x_y['y'], button='left') 129 | if double_click: 130 | time.sleep(0.1) 131 | pyautogui.click(x_y['x'], x_y['y'], button='left') 132 | 133 | 134 | return True 135 | 136 | 137 | click_to_icon = tool(click_to_icon_) 138 | 139 | 140 | def click_to_area_( 141 | area:str, double_click=False 142 | ) -> bool: 143 | """ 144 | Click on the area like search bar 145 | """ 146 | 147 | try: 148 | from .cu.ask_anthropic import ask_anthropic 149 | from .cu.computer import click_action, mouse_move_action 150 | except ImportError: 151 | from cu.ask_anthropic import ask_anthropic 152 | from cu.computer import click_action, mouse_move_action 153 | 154 | print("click_to_area") 155 | print("area", area) 156 | x_y = ask_anthropic(f"dont use tools, give me exactly location of '{area}' area as json x,y like"+ """{'x': 0, 'y': 0}"""+". Only return the json with ```json ```") 157 | print("result", x_y) 158 | 159 | x_y = extract_code_from_result(x_y) 160 | 161 | x_y = json.loads(x_y) 162 | 163 | pyautogui.click(x_y['x'], x_y['y'], button='left') 164 | if double_click: 165 | time.sleep(0.1) 166 | pyautogui.click(x_y['x'], x_y['y'], button='left') 167 | 168 | 169 | return True 170 | 171 | 172 | 173 | 174 | click_to_area = tool(click_to_area_) 175 | 176 | 177 | 178 | 179 | def screenshot_(checking:str): 180 | """ 181 | Returns the current screenshot. Explain what should we check on the screenshot. 182 | """ 183 | 184 | from langchain_core.messages import HumanMessage, SystemMessage, AIMessage 185 | 186 | try: 187 | from .cu.computer import screenshot_action 188 | from .agent.agent import get_agent_executor 189 | except ImportError: 190 | from cu.computer import screenshot_action 191 | from agent.agent import get_agent_executor 192 | 193 | the_base64 = screenshot_action(direct_base64=True) 194 | 195 | 196 | 197 | 198 | 199 | 200 | human_first_message = {"type": "text", "text": f"Explain the image and check '{checking}' on the image."} 201 | 202 | 203 | 204 | the_message = [ 205 | human_first_message 206 | ] 207 | 208 | 209 | 210 | human_second_message = { 211 | "type": "image_url", 212 | "image_url": {"url": f"data:image/png;base64,{the_base64}"}, 213 | } 214 | 215 | 216 | 217 | 218 | the_message.append(human_second_message) 219 | 220 | 221 | 222 | 223 | 224 | the_message = HumanMessage(content=the_message) 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | msg = get_agent_executor(no_tools=True).invoke( 233 | {"messages": [the_message]} 234 | ) 235 | 236 | 237 | 238 | 239 | 240 | the_last_messages = msg["messages"] 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | return_value = the_last_messages[-1].content 251 | if isinstance(return_value, list): 252 | the_text = "" 253 | for each in return_value: 254 | the_text += str(each) 255 | return_value = the_text 256 | 257 | if return_value == "": 258 | return_value = "No response " 259 | 260 | 261 | 262 | 263 | return return_value 264 | 265 | 266 | 267 | screenshot = tool(screenshot_) -------------------------------------------------------------------------------- /gpt_computer_agent/standard_tools.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | import requests 3 | import re 4 | from urllib.parse import urljoin 5 | import datetime 6 | import traceback 7 | 8 | try: 9 | from .tooler import tool 10 | from .top_bar_wrapper import wrapper 11 | except: 12 | from tooler import tool 13 | from top_bar_wrapper import wrapper 14 | 15 | _standard_tools_ = {} 16 | 17 | 18 | def register_tool(func): 19 | if func.__name__ not in _standard_tools_: 20 | _standard_tools_[func.__name__] = tool(func) 21 | return func 22 | 23 | 24 | @register_tool 25 | @wrapper 26 | def read_website(url: str, max_content_length: int = 5000) -> dict: 27 | """ 28 | Read the content of a website and return the title, meta data, content, and sub-links. 29 | """ 30 | try: 31 | response = requests.get(url) 32 | response.raise_for_status() 33 | html = response.text 34 | except requests.RequestException as e: 35 | return {"error": f"Failed to retrieve the website content: {e}"} 36 | 37 | soup = BeautifulSoup(html, "html.parser") 38 | 39 | meta_properties = [ 40 | "og:description", 41 | "og:site_name", 42 | "og:title", 43 | "og:type", 44 | "og:url", 45 | "description", 46 | "keywords", 47 | "author", 48 | ] 49 | meta = {} 50 | for property_name in meta_properties: 51 | tag = soup.find("meta", property=property_name) or soup.find( 52 | "meta", attrs={"name": property_name} 53 | ) 54 | if tag: 55 | meta[property_name] = tag.get("content", "") 56 | 57 | for ignore_tag in soup(["script", "style"]): 58 | ignore_tag.decompose() 59 | 60 | title = soup.title.string.strip() if soup.title else "" 61 | content = soup.body.get_text(separator="\n") if soup.body else "" 62 | 63 | links = [] 64 | for a in soup.find_all("a", href=True): 65 | link_url = urljoin(url, a["href"]) 66 | links.append({"title": a.text.strip(), "link": link_url}) 67 | 68 | content = re.sub(r"[\n\r\t]+", "\n", content) 69 | content = re.sub(r" +", " ", content) 70 | content = re.sub(r"[\n ]{3,}", "\n\n", content) 71 | content = content.strip() 72 | 73 | if len(content) > max_content_length: 74 | content = content[:max_content_length].rsplit(" ", 1)[0] + "..." 75 | 76 | return {"meta": meta, "title": title, "content": content, "sub_links": links} 77 | 78 | 79 | @register_tool 80 | @wrapper 81 | def google(query: str, max_number: int = 20) -> list: 82 | """ 83 | Search the query on Google and return the results. 84 | """ 85 | try: 86 | from googlesearch import search as gsearch 87 | 88 | return list(gsearch(query, stop=max_number)) 89 | except: 90 | return "An exception occurred" 91 | 92 | 93 | @register_tool 94 | @wrapper 95 | def duckduckgo(query: str, max_number: int = 20) -> list: 96 | """ 97 | Search the query on DuckDuckGo and return the results. 98 | """ 99 | try: 100 | from duckduckgo_search import DDGS 101 | 102 | return [result["href"] for result in DDGS().text(query, max_results=max_number)] 103 | except: 104 | return "An exception occurred" 105 | 106 | 107 | 108 | 109 | 110 | @register_tool 111 | @wrapper 112 | def open_url(url) -> bool: 113 | """ 114 | Open the URL in the default web browser. 115 | 116 | :param url: str: 117 | """ 118 | import webbrowser 119 | 120 | try: 121 | webbrowser.open(url) 122 | return True 123 | except: 124 | return False 125 | 126 | 127 | 128 | @register_tool 129 | @wrapper 130 | def sleep(seconds: int): 131 | """ 132 | Sleep for the given number of seconds. 133 | """ 134 | import time 135 | 136 | time.sleep(seconds) 137 | 138 | 139 | 140 | 141 | 142 | from langchain_experimental.utilities import PythonREPL 143 | 144 | the_py_client = PythonREPL() 145 | 146 | 147 | @register_tool 148 | @wrapper 149 | def python_repl(code: str) -> str: 150 | """ 151 | Run and return the given python code in python repl 152 | """ 153 | return the_py_client.run(code) 154 | 155 | 156 | 157 | @register_tool 158 | @wrapper 159 | def keyboard_write(text: str): 160 | """ 161 | Write the text using the keyboard (its use pyautogui). 162 | """ 163 | import pyautogui 164 | pyautogui.write(text) 165 | 166 | @register_tool 167 | @wrapper 168 | def keyboard_press(key: str): 169 | """ 170 | Press the key using the keyboard (its use pyautogui). 171 | """ 172 | import pyautogui 173 | pyautogui.press(key) 174 | pyautogui.press(key) 175 | 176 | 177 | 178 | 179 | @register_tool 180 | @wrapper 181 | def get_current_time() -> str: 182 | """ 183 | Get the current time in ISO format. 184 | """ 185 | return datetime.datetime.now().isoformat() 186 | 187 | 188 | @register_tool 189 | @wrapper 190 | def turn_off_wifi() -> bool: 191 | """ 192 | Turn off the wifi. 193 | """ 194 | try: 195 | from pywifi import ControlPeripheral 196 | 197 | wifi = ControlPeripheral() 198 | wifi.disable() 199 | return True 200 | except: 201 | return False 202 | 203 | 204 | @register_tool 205 | @wrapper 206 | def turn_on_wifi() -> bool: 207 | """ 208 | Turn on the wifi. 209 | """ 210 | try: 211 | from pywifi import ControlPeripheral 212 | 213 | wifi = ControlPeripheral() 214 | wifi.enable() 215 | return True 216 | except: 217 | return False 218 | 219 | 220 | @register_tool 221 | @wrapper 222 | def connect_wifi(ssid: str, password: str) -> bool: 223 | """ 224 | Connect to the wifi with the given ssid and password. 225 | """ 226 | try: 227 | from pywifi import ControlConnection 228 | 229 | # Arguments passed during object instantiation 230 | controller = ControlConnection(wifi_ssid=ssid, wifi_password=password) 231 | controller.wifi_connector() 232 | return True 233 | except: 234 | return False 235 | 236 | 237 | 238 | import subprocess 239 | 240 | 241 | @register_tool 242 | @wrapper 243 | def run_terminal_command(command:str) -> str: 244 | """ 245 | Executes a terminal command and returns the result. 246 | 247 | Args: 248 | command (str): The command to run in the terminal. 249 | 250 | Returns: 251 | str: The output of the command. 252 | """ 253 | try: 254 | result = subprocess.run(command, shell=True, capture_output=True, text=True) 255 | return result.stdout.strip() 256 | except Exception as e: 257 | return str(e) 258 | 259 | 260 | 261 | def get_standard_tools(): 262 | print("Tool len", len(_standard_tools_)) 263 | last_list = [_standard_tools_[each] for each in _standard_tools_] 264 | return last_list 265 | 266 | 267 | if __name__ == "__main__": 268 | print(ask_to_user("What is your age")) 269 | -------------------------------------------------------------------------------- /gpt_computer_agent/agent/assistant.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | from langchain_core.messages import HumanMessage, SystemMessage, AIMessage 5 | 6 | from .chat_history import * 7 | from .agent import * 8 | 9 | 10 | try: 11 | from ..screen.shot import * 12 | from ..utils.db import load_model_settings, agents 13 | from ..llm import get_model 14 | from ..llm_settings import llm_settings 15 | from ..utils.chat_history import ChatHistory 16 | except ImportError: 17 | from screen.shot import * 18 | from utils.db import load_model_settings, agents 19 | from utils.chat_history import ChatHistory 20 | from llm import get_model 21 | from llm_settings import llm_settings 22 | 23 | config = {"configurable": {"thread_id": "abc123"}, "recursion_limit":100} 24 | 25 | 26 | 27 | def random_charachter(length=10): 28 | return "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=length)) 29 | 30 | 31 | 32 | def agentic( 33 | llm_input, llm_history, client, screenshot_path=None, dont_save_image=False 34 | ): 35 | global agents 36 | from crewai import Task, Crew 37 | 38 | from crewai import Agent as crewai_Agent 39 | 40 | the_agents = [] 41 | 42 | for each in agents: 43 | the_agents.append( 44 | crewai_Agent( 45 | role=each["role"], 46 | goal=each["goal"], 47 | backstory=each["backstory"], 48 | llm=get_model(high_context=True), 49 | ) 50 | ) 51 | 52 | agents = the_agents 53 | 54 | print("LLM INPUT", llm_input) 55 | 56 | def image_explaination(): 57 | the_message = [ 58 | {"type": "text", "text": "Explain the image"}, 59 | ] 60 | 61 | if screenshot_path: 62 | base64_image = encode_image(screenshot_path) 63 | the_message.append( 64 | { 65 | "type": "image_url", 66 | "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}, 67 | }, 68 | ) 69 | print("LEN OF İMAGE", len(base64_image)) 70 | 71 | the_message = HumanMessage(content=the_message) 72 | get_chat_message_history().add_message(the_message) 73 | 74 | the_model = load_model_settings() 75 | 76 | if ( 77 | llm_settings[the_model]["provider"] == "openai" 78 | or llm_settings[the_model]["provider"] == "ollama" 79 | or llm_settings[the_model]["provider"] == "azureai" 80 | or llm_settings[the_model]["provider"] == "anthropic" 81 | or llm_settings[the_model]["provider"] == "aws" 82 | ): 83 | msg = get_agent_executor().invoke( 84 | {"messages": llm_history + [the_message]}, config=config 85 | ) 86 | 87 | if llm_settings[the_model]["provider"] == "google": 88 | msg = get_agent_executor().invoke( 89 | {"messages": llm_history + [the_message]}, config=config 90 | ) 91 | 92 | the_last_messages = msg["messages"] 93 | 94 | return the_last_messages[-1].content 95 | 96 | if screenshot_path: 97 | image_explain = image_explaination() 98 | llm_input += "User Sent Image and image content is: " + image_explain 99 | 100 | llm_input = llm_input 101 | 102 | task = Task( 103 | description=llm_input, 104 | expected_output="Answer", 105 | agent=agents[0], 106 | tools=get_tools(), 107 | ) 108 | 109 | the_crew = Crew( 110 | agents=agents, 111 | tasks=[task], 112 | full_output=True, 113 | verbose=True, 114 | ) 115 | 116 | result = the_crew.kickoff()["final_output"] 117 | 118 | get_chat_message_history().add_message( 119 | HumanMessage(content=[llm_input]) 120 | ) 121 | get_chat_message_history().add_message(AIMessage(content=[result])) 122 | 123 | return result 124 | 125 | 126 | def assistant( 127 | llm_input, client, screenshot_path=None, dont_save_image=False, just_screenshot=False 128 | ): 129 | 130 | the_chat_history = ChatHistory() 131 | 132 | the_model = load_model_settings() 133 | 134 | print("LLM INPUT", llm_input) 135 | 136 | if llm_settings[the_model]["tools"]: 137 | llm_input = llm_input 138 | 139 | 140 | human_first_message = {"type": "text", "text": f"{llm_input}"} 141 | the_chat_history.add_message("human", human_first_message) 142 | 143 | 144 | the_message = [ 145 | human_first_message 146 | ] 147 | 148 | 149 | 150 | human_second_message = None 151 | 152 | if screenshot_path: 153 | base64_image = encode_image(screenshot_path) 154 | if llm_settings[the_model]["provider"] == "ollama": 155 | 156 | human_second_message = { 157 | "type": "image_url", 158 | "image_url": base64_image, 159 | } 160 | the_chat_history.add_message("human", human_second_message, auto_delete=10) 161 | 162 | 163 | else: 164 | human_second_message = { 165 | "type": "image_url", 166 | "image_url": {"url": f"data:image/png;base64,{base64_image}"}, 167 | } 168 | the_chat_history.add_message("human", human_second_message, auto_delete=10) 169 | 170 | 171 | print("LEN OF IMAGE", len(base64_image)) 172 | 173 | 174 | if human_second_message: 175 | the_message.append(human_second_message) 176 | 177 | 178 | 179 | 180 | 181 | the_message = HumanMessage(content=the_message) 182 | 183 | 184 | 185 | 186 | llm_history = the_chat_history.get_chat() 187 | 188 | 189 | 190 | 191 | 192 | 193 | if ( 194 | llm_settings[the_model]["provider"] == "openai" 195 | or llm_settings[the_model]["provider"] == "azureai" 196 | or llm_settings[the_model]["provider"] == "anthropic" 197 | or llm_settings[the_model]["provider"] == "aws" 198 | ): 199 | if just_screenshot: 200 | msg = {"messages": llm_history + [the_message]} 201 | time.sleep(1) 202 | else: 203 | print("The model:", the_model) 204 | msg = get_agent_executor().invoke( 205 | {"messages": llm_history + [the_message]}, config=config 206 | ) 207 | 208 | 209 | 210 | 211 | 212 | the_last_messages = msg["messages"] 213 | 214 | 215 | 216 | the_chat_history.add_message("assistant", the_last_messages[-1].content) 217 | 218 | 219 | 220 | 221 | 222 | 223 | return_value = the_last_messages[-1].content 224 | if isinstance(return_value, list): 225 | the_text = "" 226 | for each in return_value: 227 | the_text += str(each) 228 | return_value = the_text 229 | 230 | if return_value == "": 231 | return_value = "No response " 232 | return_value += str(random_charachter()) 233 | 234 | 235 | if just_screenshot: 236 | return "OK" 237 | 238 | return return_value 239 | -------------------------------------------------------------------------------- /gpt_computer_agent/utils/chat_history.py: -------------------------------------------------------------------------------- 1 | 2 | from kot import KOT 3 | 4 | try: 5 | from .folder import currently_dir, artifacts_dir, media_dir 6 | except: 7 | from folder import currently_dir, artifacts_dir, media_dir 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | from langchain_core.messages import HumanMessage, SystemMessage, AIMessage 18 | 19 | 20 | try: 21 | from .db import * 22 | 23 | except: 24 | from db import * 25 | 26 | 27 | import time 28 | 29 | 30 | 31 | class Human: 32 | def __init__(self, content, the_time, auto_delete:int=None): 33 | 34 | self.that_was_empty = False 35 | 36 | if isinstance(content, dict): 37 | if "text" in content: 38 | if content["text"] == "": 39 | self.that_was_empty = True 40 | content["text"] = "No response" 41 | 42 | if isinstance(content, list): 43 | for i in range(len(content)): 44 | if "text" in content[i]: 45 | if content[i]["text"] == "": 46 | self.that_was_empty = True 47 | content[i]["text"] = "No response" 48 | 49 | 50 | self.content = content 51 | self.timestamp = the_time 52 | self.auto_delete = auto_delete 53 | 54 | def __dict__(self): 55 | current_time = time.time() 56 | 57 | if self.auto_delete is not None: 58 | print(current_time, self.timestamp, self.auto_delete) 59 | if current_time - self.timestamp > self.auto_delete: 60 | return {"type": "human", "content": "This content deleted.", "timestamp": self.timestamp, "auto_delete": self.auto_delete, "that_was_empty": self.that_was_empty} 61 | 62 | return {"type": "human", "content": self.content, "timestamp": self.timestamp, "auto_delete": self.auto_delete, "that_was_empty": self.that_was_empty} 63 | 64 | class Assistant: 65 | def __init__(self, content, the_time): 66 | 67 | self.that_was_empty = False 68 | 69 | if isinstance(content, dict): 70 | if "text" in content: 71 | if content["text"] == "": 72 | self.that_was_empty = True 73 | content["text"] = "No response" 74 | 75 | if isinstance(content, list): 76 | for i in range(len(content)): 77 | if "text" in content[i]: 78 | if content[i]["text"] == "": 79 | self.that_was_empty = True 80 | content[i]["text"] = "No response" 81 | self.content = content 82 | self.timestamp = the_time 83 | 84 | def __dict__(self): 85 | return {"type": "assistant", "content": self.content, "timestamp": self.timestamp, "that_was_empty": self.that_was_empty} 86 | 87 | class System: 88 | def __init__(self, content, the_time): 89 | 90 | self.that_was_empty = False 91 | 92 | if isinstance(content, dict): 93 | if "text" in content: 94 | if content["text"] == "": 95 | self.that_was_empty = True 96 | content["text"] = "No response" 97 | 98 | if isinstance(content, list): 99 | for i in range(len(content)): 100 | if "text" in content[i]: 101 | if content[i]["text"] == "": 102 | self.that_was_empty = True 103 | content[i]["text"] = "No response" 104 | 105 | 106 | self.content = content 107 | self.timestamp = the_time 108 | 109 | def __dict__(self): 110 | return {"type": "system", "content": self.content, "timestamp": self.timestamp, "that_was_empty": self.that_was_empty} 111 | 112 | 113 | class ChatHistory: 114 | 115 | def __init__(self): 116 | self.chat_id = get_profile() 117 | self.db = KOT(f"chat_history_{self.chat_id}", folder=artifacts_dir, enable_hashing=True) 118 | 119 | if self.db.get("chat") is None: 120 | self.db.set("chat", []) 121 | 122 | if self.get_chat() == []: 123 | print("SETTING CHAT") 124 | self.add_message("system", {"type":"text", "text": load_system_prompt()}) 125 | 126 | 127 | def add_message(self, message_type:str, content, auto_delete:int=None): 128 | 129 | the_time = time.time() 130 | 131 | 132 | if content == []: 133 | content = {"type":"text", "text": "No response"} 134 | 135 | 136 | if message_type == "human": 137 | message = Human(content, the_time, auto_delete) 138 | elif message_type == "assistant": 139 | print("ASSISTANT", content) 140 | message = Assistant(content, the_time) 141 | elif message_type == "system": 142 | print("SYSTEM", content) 143 | message = System(content, the_time) 144 | else: 145 | raise ValueError("Invalid message type") 146 | 147 | 148 | chat = self.db.get("chat") 149 | chat.append(message.__dict__()) 150 | 151 | 152 | self.db.set("chat", chat) 153 | 154 | def get_chat(self): 155 | chat = self.db.get("chat") 156 | chat = sorted(chat, key=lambda x: x["timestamp"]) 157 | 158 | print("CHAT", chat) 159 | # Transform dict to Message objects 160 | 161 | the_chat = [] 162 | for message in chat: 163 | if message["type"] == "human": 164 | the_chat.append(Human(content=message["content"], the_time=message["timestamp"], auto_delete=message["auto_delete"])) 165 | elif message["type"] == "assistant": 166 | the_chat.append(Assistant(content=message["content"], the_time=message["timestamp"])) 167 | elif message["type"] == "system": 168 | the_chat.append(System(content=message["content"], the_time=message["timestamp"])) 169 | 170 | 171 | last_chat = [] 172 | for message in the_chat: 173 | if message.that_was_empty: 174 | continue 175 | last_chat.append(message.__dict__()) 176 | 177 | 178 | chat = last_chat 179 | 180 | langchain_messages = [] 181 | 182 | for message in chat: 183 | 184 | if isinstance(message["content"], tuple): 185 | message["content"] = list(message["content"]) 186 | if isinstance(message["content"], dict): 187 | message["content"] = [message["content"]] 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | if message["type"] == "human": 198 | 199 | langchain_messages.append(HumanMessage(content= 200 | message["content"] 201 | )) 202 | elif message["type"] == "assistant": 203 | langchain_messages.append(AIMessage(content= 204 | message["content"] 205 | )) 206 | elif message["type"] == "system": 207 | langchain_messages.append(SystemMessage(content= 208 | message["content"] 209 | )) 210 | 211 | 212 | return langchain_messages 213 | 214 | 215 | def clear_chat(self): 216 | self.db.set("chat", []) 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /README.zh_TW.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | <p align="center"> 4 | <a href="#"> 5 | <img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/176c8ddb-219e-444e-8782-1f8c37a92678" alt="Logo" width="250" > 6 | </a> 7 | 8 | <h3 align="center">GPT 電腦助手</h3> 9 | <p align="center"> 10 | <a href="https://discord.gg/qApFmWMt8x"><img alt="Static Badge" src="https://img.shields.io/discord/1148697961639968859.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2" width=100></a> 11 | </p> 12 | 13 | <p align="center"> 14 | 適用於 Windows、MacOS 和 Ubuntu 的 gpt-4o 15 | <br /> 16 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"><strong>文件</strong></a> 17 | . 18 | <a href="https://github.com/khulnasoft/gpt-computer-agent/#Capabilities"><strong>探索功能 »</strong></a> 19 | <br /> 20 | </p> 21 | <br> 22 | <p align="center"> 23 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"> 24 | <img src="https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white" alt="windows"> 25 | </a> 26 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"> 27 | <img src="https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=apple&logoColor=white" alt="macos"> 28 | </a> 29 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki"> 30 | <img src="https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black" alt="linux"> 31 | </a> 32 | <br> 33 | 34 | </p> 35 | <p align="center"> 36 | <a href="https://www.python.org/"> 37 | <img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg" alt="Made_with_python"> 38 | </a> 39 | . 40 | <img src="https://static.pepy.tech/personalized-badge/gpt-computer-agent?period=total&units=international_system&left_color=grey&right_color=blue&left_text=PyPI%20Downloads" alt="pypi_downloads"> 41 | </p> 42 | 43 | 44 | <p align="center"> 45 | <a href="https://x.com/GPTCompAsst"><img alt="Static Badge" src="https://img.shields.io/twitter/follow/GPTCompAsst?style=social" width=160></a> 46 | </p> 47 | 48 | |[ENGLISH](README.md)|[簡體中文](README.zh_CN.md)|正體中文|[TÜRKÇE](README.TR.md) 49 | 50 | # GPT 電腦助手 51 | 嗨,這是為了將 ChatGPT MacOS 應用程式提供給 Windows 和 Linux 的替代方案。這樣做可以提供一個新鮮且穩定的解決方案。這次您可以輕鬆地安裝為 Python 庫,但我們將準備一個流程,提供本機安裝腳本(.exe)。 52 | 53 | 由 <a href="https://github.com/KhulnaSoft/Tiger"><strong>KhulnaSoft Tiger 🐅</strong></a> 提供支持的功能集成中心。 54 | 55 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki/Usage"><img alt="Static Badge" src="https://img.shields.io/badge/Local_Models-Available-blue" width=150></a> 56 | <br> 57 | <a href="https://github.com/khulnasoft/gpt-computer-agent/wiki/Usage"><img alt="Static Badge" src="https://img.shields.io/badge/Groq-Available-blue" width=100></a> 58 | 59 | 60 | 61 | ## 安裝 && 運行 62 | 需要 >= Python 3.9 63 | ```console 64 | pip3 install 'gpt-computer-agent[default]' 65 | ``` 66 | 67 | ```console 68 | computeragent 69 | ``` 70 | 71 | ### 代理基礎設施 72 | 73 | 這樣一來,您可以創建 `crewai` 代理,並將其用於 gpt-computer-agent 圖形用戶界面和工具中。 74 | 75 | 76 | ```console 77 | pip3 install 'gpt-computer-agent[agentic]' 78 | ``` 79 | 80 | ```python 81 | from gpt_computer_agent import Agent, start 82 | 83 | manager = Agent( 84 | role='Project Manager', 85 | goal='understands project needs and assist coder', 86 | backstory="""You're a manager at a large company.""", 87 | ) 88 | 89 | coder = Agent( 90 | role='Senior Python Coder', 91 | goal='writing python scripts and copying to clipboard', 92 | backstory="""You're a python developer at a large company.""", 93 | ) 94 | 95 | 96 | start() 97 | ``` 98 | 99 | 100 | ### 新增自訂工具 101 | 102 | 現在您可以添加在代理基礎設施和助理進程中運行的自訂工具。 103 | 104 | 105 | ```python 106 | from gpt_computer_agent import Tool, start 107 | 108 | @Tool 109 | def sum_tool(first_number: int, second_number: int) -> str: 110 | """Useful for when you need to sum two numbers together.""" 111 | return first_number + second_number 112 | 113 | start() 114 | ``` 115 | 116 | 117 | https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/26ae3624-e619-44d6-9b04-f39cf1ac1f8f 118 | 119 | <p align="center"> 120 | <a href="#"> 121 | <img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/94ac619c-1f29-4fe6-b3cb-85a03932646b" alt="Logo" > 122 | </a> 123 | </p> 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | ## 使用方式 132 | ![選項](https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/54b39347-98e0-4ee4-a715-9128c40dbcd4) 133 | 134 | 135 | ## 使用案例 136 | 137 | <table> 138 | <tr> 139 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/b4a4f11e-5588-4656-b5d7-b612a9a2855b" alt="Take Meeting Notes" width="500"/></td> 140 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/49eeac70-b33a-4ec4-8125-64127621ed62" alt="Daily Assistant" width="500"/></td> 141 | </tr> 142 | <tr> 143 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/10b69a18-033c-4d81-8ac9-f4e3c65b59c3" alt="Read Docs" width="500"/></td> 144 | <td><img src="https://github.com/khulnasoft/gpt-computer-agent/assets/41792982/0f483bae-ffaf-4311-8653-c0dc64fb5ebe" alt="Coding Assistant" width="500"/></td> 145 | 146 | </tr> 147 | </table> 148 | 149 | 150 | 151 | 152 | 153 | 154 | ## 路線圖 155 | 156 | | 功能 | 狀態 | 目標發布 | 157 | |---------------------------------|--------------|--------------| 158 | | 清除聊天記錄 | 已完成 | 2024 年第二季度| 159 | | 長音訊支持(拆分 20mb) | 已完成 | 2024 年第二季度| 160 | | 文本輸入 | 已完成 | 2024 年第二季度| 161 | | 僅文本模式(靜音) | 已完成 | 2024 年第二季度| 162 | | 添加配置文件(不同聊天) | 已完成 | 2024 年第二季度| 163 | | 更多關於助手狀態的回饋 | 已完成 | 2024 年第二季度| 164 | | **新 UI** | 計劃中 | 2024 年第二季度| 165 | | **我們的自訂代理基礎設施** | 計劃中 | 2024 年第二季度| 166 | | **本機應用程式,exe,dmg,appimage** | 計劃中 | 2024 年第二季度| 167 | | **DeepFace 集成(臉部識別)** | 計劃中 | 2024 年第二季度| 168 | | **本地模式(使用 Ollama,語音和視覺模型)** | 計劃中 | 2024 年第二季度| 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | ## 功能 178 | 此時我們擁有許多基礎設施元素。我們只是希望提供 ChatGPT 應用中已經存在的所有功能。 179 | 180 | | 功能 | 描述 | 181 | |-----------------------------------|-------------------------------| 182 | | **螢幕讀取** | OK | 183 | | **麥克風** | OK | 184 | | **系統音訊** | OK | 185 | | **記憶體** | OK | 186 | | **打開和關閉應用程式** | OK | 187 | | **打開一個 URL** | OK | 188 | | **剪貼簿** | OK | 189 | | **搜尋引擎** | OK | 190 | | **編寫和運行 Python** | OK | 191 | | **編寫和運行 SH** | OK | 192 | | **使用你的 Telegram 帳戶** | OK | 193 | | **知識管理** | OK | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | ## 貢獻者 205 | 206 | <a href="https://github.com/khulnasoft/gpt-computer-agent/graphs/contributors"> 207 | <img src="https://contrib.rocks/image?repo=khulnasoft/gpt-computer-agent" /> 208 | </a> 209 | -------------------------------------------------------------------------------- /gpt_computer_agent/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .start import start 3 | 4 | from .agentic import Agent 5 | 6 | from .tooler import Tool 7 | except: 8 | pass 9 | __version__ = '0.28.3' # fmt: skip 10 | 11 | 12 | 13 | 14 | 15 | from .classes import BaseClass, BaseVerifier, TypeVerifier, Task 16 | 17 | import os 18 | import time 19 | import subprocess 20 | import requests 21 | 22 | 23 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 24 | 25 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 26 | 27 | 28 | 29 | class instance: 30 | def __init__(self, url, tasks=[]): 31 | self.url = url 32 | self.task = [] 33 | for t in tasks: 34 | self.add_task(t) 35 | 36 | 37 | def request(self): 38 | pass 39 | 40 | def add_task(self, task): 41 | if isinstance(task, list): 42 | for t in task: 43 | self.task.append(t) 44 | else: 45 | self.task.append(task) 46 | 47 | for t in self.task: 48 | t.add_client(self) 49 | 50 | def kick(self): 51 | for t in self.task: 52 | t.run() 53 | 54 | results = [] 55 | for t in self.task: 56 | results.append(t.result) 57 | 58 | return results 59 | 60 | 61 | def run(self, task): 62 | task.add_client(self) 63 | task.run() 64 | return task.result 65 | 66 | 67 | def user_id(self): 68 | from .utils.user_id import load_user_id 69 | return load_user_id() 70 | 71 | 72 | class interface: 73 | pass 74 | 75 | 76 | 77 | class local_instance(instance): 78 | def __init__(self, *args, **kwargs): 79 | super().__init__("http://localhost:7541", *args, **kwargs) 80 | from .remote import Remote_Client 81 | 82 | self.client = Remote_Client(self.url) 83 | 84 | def request(self, the_request, the_response, screen=False): 85 | 86 | return self.client.request(the_request, the_response, screen) 87 | 88 | 89 | def start(self): 90 | command = "python -c 'from gpt_computer_agent import start; start(True);'" 91 | self.process = subprocess.Popen(command, shell=True) 92 | 93 | 94 | def close(self): 95 | try: 96 | self.client.stop_server() 97 | except: 98 | pass 99 | 100 | self.process.terminate() 101 | self.process.wait() 102 | 103 | 104 | 105 | def client_status(self): 106 | return self.client.status 107 | 108 | 109 | 110 | 111 | class local(interface): 112 | 113 | @staticmethod 114 | def agent( *args, **kwargs): 115 | the_instance = local_instance( *args, **kwargs) 116 | the_instance.start() 117 | 118 | time.sleep(5) 119 | 120 | client_status = the_instance.client_status() 121 | 122 | if not client_status: 123 | raise Exception("Failed to start the local instance") 124 | 125 | return the_instance 126 | 127 | 128 | 129 | class cloud_instance(instance): 130 | def __init__(self, *args, **kwargs): 131 | super().__init__("https://free_cloud_1.gca.dev/", *args, **kwargs) 132 | 133 | 134 | def request(self, the_request, the_response, screen=False): 135 | screen = "false" if not screen else "true" 136 | 137 | response = requests.post(self.url+"request", data={"request": the_request, "response": the_response, "screen":screen, "instance":self.instance_id}, verify=False) 138 | json_response = response.json() 139 | request_id = json_response["request_id"] 140 | try: 141 | while True: 142 | response = requests.post(self.url+"request_result", data={"request_id": request_id}, verify=False) 143 | the_json = response.json() 144 | if the_json["status"] == True: 145 | return the_json["result"] 146 | time.sleep(1) 147 | except: 148 | return response.text 149 | 150 | 151 | 152 | def change_profile(self, profile): 153 | response = requests.post(self.url+"change_profile", data={"profile": profile, "instance":self.instance_id}, verify=False) 154 | the_json = response.json() 155 | return the_json["result"] 156 | 157 | def add_system_message(self, system_message): 158 | response = requests.post(self.url+"add_system_message", data={"system_message": system_message, "instance":self.instance_id}, verify=False) 159 | the_json = response.json() 160 | return the_json["result"] 161 | 162 | 163 | def add_user_id(self, user_id): 164 | response = requests.post(self.url+"add_user_id", data={"user_id": user_id, "instance":self.instance_id}, verify=False) 165 | the_json = response.json() 166 | return the_json["result"] 167 | 168 | def get_logs(self): 169 | response = requests.post(self.url+"get_logs", data={"instance":self.instance_id}, verify=False) 170 | the_json = response.json() 171 | return the_json["result"] 172 | 173 | def reset_memory(self): 174 | response = requests.post(self.url+"reset_memory", data={"instance":self.instance_id}, verify=False) 175 | the_json = response.json() 176 | return the_json["result"] 177 | 178 | def screenshot(self): 179 | response = requests.post(self.url+"screenshot_instance", data={"instance":self.instance_id}, verify=False) 180 | 181 | its_an_error = False 182 | 183 | try: 184 | the_json = response.json() 185 | if "result" in the_json: 186 | its_an_error = True 187 | except: 188 | pass 189 | 190 | 191 | if not its_an_error: 192 | with open('current_screenshot.png', 'wb') as file: 193 | file.write(response.content) 194 | import matplotlib.pyplot as plt 195 | import matplotlib.image as mpimg 196 | 197 | img = mpimg.imread('current_screenshot.png') 198 | plt.imshow(img) 199 | plt.axis('off') 200 | plt.show() 201 | 202 | 203 | 204 | 205 | 206 | def start(self): 207 | req = requests.get(self.url+"start_instance", verify=False) 208 | the_json = req.json() 209 | 210 | self.instance_id = the_json["result"] 211 | self.add_user_id(self.user_id()) 212 | 213 | 214 | 215 | def close(self): 216 | req = requests.post(self.url+"stop_instance", data={"instance": self.instance_id}, verify=False) 217 | the_json = req.json() 218 | return the_json["result"] 219 | 220 | def client_status(self): 221 | return True 222 | 223 | 224 | 225 | class Cloud(interface): 226 | 227 | @staticmethod 228 | def agent(*args, **kwargs): 229 | start_time = time.time() 230 | 231 | the_instance = cloud_instance( *args, **kwargs) 232 | the_instance.start() 233 | time.sleep(1) 234 | 235 | end_time = time.time() 236 | 237 | print(f"Time to start the instance: {end_time - start_time}") 238 | 239 | return the_instance 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | class docker_instance(instance): 248 | def __init__(self, url, *args, **kwargs): 249 | super().__init__(url, *args, **kwargs) 250 | from .remote import Remote_Client 251 | 252 | self.client = Remote_Client(self.url) 253 | 254 | def request(self, the_request, the_response, screen=False): 255 | 256 | return self.client.request(the_request, the_response, screen) 257 | 258 | 259 | def start(self): 260 | pass 261 | 262 | 263 | def close(self): 264 | pass 265 | 266 | 267 | 268 | def client_status(self): 269 | return self.client.status 270 | 271 | 272 | 273 | 274 | class docker(interface): 275 | 276 | @staticmethod 277 | def agent(url, *args, **kwargs): 278 | the_instance = docker_instance(url, *args, **kwargs) 279 | the_instance.start() 280 | 281 | 282 | client_status = the_instance.client_status() 283 | 284 | if not client_status: 285 | raise Exception("Failed to start the docker instance") 286 | 287 | return the_instance 288 | -------------------------------------------------------------------------------- /gpt_computer_agent/cu/computer.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ..utils.db import * 3 | from ..llm import get_model 4 | from ..top_bar_wrapper import wrapper 5 | from ..llm_settings import llm_settings 6 | except ImportError: 7 | from utils.db import * 8 | from top_bar_wrapper import wrapper 9 | from llm_settings import llm_settings 10 | 11 | from langchain.tools import tool 12 | 13 | 14 | import base64 15 | import math 16 | import os 17 | import platform 18 | import shlex 19 | import shutil 20 | import tempfile 21 | import time 22 | from strenum import StrEnum 23 | from pathlib import Path 24 | from typing import Literal, TypedDict 25 | from uuid import uuid4 26 | import pyautogui 27 | from anthropic.types.beta import BetaToolComputerUse20241022Param 28 | from PIL import Image 29 | 30 | from .base import BaseAnthropicTool, ToolError, ToolResult 31 | from .run import run 32 | 33 | OUTPUT_DIR = "/tmp/outputs" 34 | 35 | TYPING_DELAY_MS = 12 36 | TYPING_GROUP_SIZE = 50 37 | 38 | Action = Literal[ 39 | "key", 40 | "type", 41 | "mouse_move", 42 | "left_click", 43 | "left_click_drag", 44 | "right_click", 45 | "middle_click", 46 | "double_click", 47 | "screenshot", 48 | "cursor_position", 49 | ] 50 | 51 | 52 | class Resolution(TypedDict): 53 | width: int 54 | height: int 55 | 56 | 57 | MAX_SCALING_TARGETS: dict[str, Resolution] = { 58 | "XGA": Resolution(width=1024, height=768), 59 | "WXGA": Resolution(width=1280, height=800), 60 | "FWXGA": Resolution(width=1366, height=768), 61 | } 62 | 63 | 64 | class ScalingSource(StrEnum): 65 | COMPUTER = "computer" 66 | API = "api" 67 | 68 | 69 | class ComputerToolOptions(TypedDict): 70 | display_height_px: int 71 | display_width_px: int 72 | display_number: int | None 73 | 74 | 75 | def chunks(s: str, chunk_size: int) -> list[str]: 76 | return [s[i : i + chunk_size] for i in range(0, len(s), chunk_size)] 77 | 78 | 79 | def smooth_move_to(x, y, duration=1.2): 80 | 81 | pyautogui.moveTo(x, y) 82 | 83 | 84 | def key_action(text: str): 85 | if platform.system() == "Darwin": 86 | text = text.replace("super+", "command+") 87 | 88 | def normalize_key(key): 89 | key = key.lower().replace("_", "") 90 | key_map = { 91 | "pagedown": "pgdn", 92 | "pageup": "pgup", 93 | "enter": "return", 94 | "return": "enter", 95 | } 96 | return key_map.get(key, key) 97 | 98 | keys = [normalize_key(k) for k in text.split("+")] 99 | 100 | if len(keys) > 1: 101 | if "darwin" in platform.system().lower(): 102 | keystroke, modifier = (keys[-1], "+".join(keys[:-1])) 103 | modifier = modifier.lower() + " down" 104 | if keystroke.lower() == "space": 105 | keystroke = " " 106 | elif keystroke.lower() == "enter": 107 | keystroke = "\n" 108 | script = f""" 109 | tell application "System Events" 110 | keystroke "{keystroke}" using {modifier} 111 | end tell 112 | """ 113 | os.system("osascript -e '{}'".format(script)) 114 | else: 115 | pyautogui.hotkey(*keys) 116 | else: 117 | pyautogui.press(keys[0]) 118 | 119 | 120 | def type_action(text: str): 121 | pyautogui.write(text, interval=TYPING_DELAY_MS / 1000) 122 | 123 | 124 | def click_action(action: str): 125 | time.sleep(0.1) 126 | button = { 127 | "left_click": "left", 128 | "right_click": "right", 129 | "middle_click": "middle", 130 | } 131 | if action == "double_click": 132 | pyautogui.click() 133 | time.sleep(0.1) 134 | pyautogui.click() 135 | else: 136 | pyautogui.click(button=button.get(action, "left")) 137 | 138 | 139 | def screenshot_action(direct_base64: bool = False) -> ToolResult: 140 | """ 141 | See the screenshot of the current screen. 142 | """ 143 | 144 | 145 | temp_dir = Path(tempfile.gettempdir()) 146 | path = temp_dir / f"screenshot_{uuid4().hex}.png" 147 | 148 | screenshot = pyautogui.screenshot() 149 | screenshot.save(str(path)) 150 | 151 | if _scaling_enabled: 152 | x, y = scale_coordinates(ScalingSource.COMPUTER, width, height) 153 | print(f"Scaling screenshot to {x}x{y}") 154 | with Image.open(path) as img: 155 | img = img.resize((x, y), Image.Resampling.LANCZOS) 156 | img.save(path) 157 | 158 | if path.exists(): 159 | 160 | with open(path, "rb") as image_file: 161 | base64_image = base64.b64encode(image_file.read()).decode("utf-8") 162 | 163 | path.unlink() 164 | if direct_base64: 165 | return base64_image 166 | return ToolResult(base64_image=base64_image) 167 | raise ToolError(f"Failed to take screenshot") 168 | 169 | 170 | def screenshot_action_(path): 171 | 172 | 173 | screenshot = pyautogui.screenshot() 174 | screenshot.save(str(path)) 175 | 176 | if _scaling_enabled: 177 | x, y = scale_coordinates(ScalingSource.COMPUTER, width, height) 178 | print(f"Scaling screenshot to {x}x{y}") 179 | with Image.open(path) as img: 180 | img = img.resize((x, y), Image.Resampling.LANCZOS) 181 | img.save(path) 182 | 183 | 184 | 185 | 186 | def cursor_position_action(): 187 | """ 188 | Get the current position of the cursor as (x, y). 189 | """ 190 | 191 | x, y = pyautogui.position() 192 | x, y = scale_coordinates(ScalingSource.COMPUTER, x, y) 193 | return ToolResult(output=f"X={x},Y={y}") 194 | 195 | 196 | def shell_action(command: str, take_screenshot=True) -> ToolResult: 197 | """ 198 | Run a shell command and return the output. 199 | """ 200 | _, stdout, stderr = run(command) 201 | base64_image = None 202 | 203 | if take_screenshot: 204 | time.sleep(_screenshot_delay) 205 | base64_image = screenshot_action().base64_image 206 | 207 | return ToolResult(output=stdout, error=stderr, base64_image=base64_image) 208 | 209 | 210 | def scale_coordinates(source: ScalingSource, x: int, y: int): 211 | if not _scaling_enabled: 212 | return x, y 213 | ratio = width / height 214 | target_dimension = None 215 | for dimension in MAX_SCALING_TARGETS.values(): 216 | if abs(dimension["width"] / dimension["height"] - ratio) < 0.02: 217 | if dimension["width"] < width: 218 | target_dimension = dimension 219 | break 220 | if target_dimension is None: 221 | return x, y 222 | x_scaling_factor = target_dimension["width"] / width 223 | y_scaling_factor = target_dimension["height"] / height 224 | if source == ScalingSource.API: 225 | if x > width or y > height: 226 | raise ToolError(f"Coordinates {x}, {y} are out of bounds") 227 | return round(x / x_scaling_factor), round(y / y_scaling_factor) 228 | return round(x * x_scaling_factor), round(y * y_scaling_factor) 229 | 230 | 231 | @wrapper 232 | def mouse_move_action(coordinate: tuple[int, int]): 233 | """Move the mouse to the specified coordinate.""" 234 | if coordinate is None: 235 | raise ToolError("coordinate is required for mouse_move") 236 | x, y = scale_coordinates(ScalingSource.API, coordinate[0], coordinate[1]) 237 | smooth_move_to(x, y) 238 | 239 | @wrapper 240 | def left_click_drag_action(coordinate: tuple[int, int]): 241 | """Perform a left click and drag to the specified coordinate.""" 242 | if coordinate is None: 243 | raise ToolError("coordinate is required for left_click_drag") 244 | x, y = scale_coordinates(ScalingSource.API, coordinate[0], coordinate[1]) 245 | smooth_move_to(x, y) 246 | pyautogui.dragTo(x, y, button="left") 247 | 248 | @wrapper 249 | def key_action_handler(text: str): 250 | """Press a specific key.""" 251 | if text is None: 252 | raise ToolError("text is required for key") 253 | key_action(text) 254 | 255 | @wrapper 256 | def type_action_handler(text: str): 257 | """Type the specified text.""" 258 | if text is None: 259 | raise ToolError("text is required for type") 260 | type_action(text) 261 | 262 | @wrapper 263 | def left_click_action(): 264 | """Perform a left click.""" 265 | click_action("left_click") 266 | 267 | @wrapper 268 | def right_click_action(): 269 | """Perform a right click.""" 270 | click_action("right_click") 271 | 272 | @wrapper 273 | def middle_click_action(): 274 | """Perform a middle click.""" 275 | click_action("middle_click") 276 | 277 | @wrapper 278 | def double_click_action(): 279 | """Perform a double click.""" 280 | click_action("double_click") 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | # Initialize global variables 290 | width, height = pyautogui.size() 291 | 292 | display_num = None 293 | _screenshot_delay = 2.0 294 | _scaling_enabled = True 295 | 296 | 297 | 298 | computer_tool = [tool(mouse_move_action), tool(left_click_drag_action), tool(key_action_handler), tool(type_action_handler), tool(left_click_action), tool(right_click_action), tool(middle_click_action), tool(double_click_action), tool(screenshot_action), tool(cursor_position_action), tool(shell_action)] 299 | -------------------------------------------------------------------------------- /gpt_computer_agent/mcp/mcp_langchain.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Requirements" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": { 14 | "vscode": { 15 | "languageId": "plaintext" 16 | } 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "!pip3 install pydantic==2.10.3 langchain-core==0.3.22 langchain==0.3.10 langchain-mcp==0.1.0a1" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# Integraion" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "vscode": { 35 | "languageId": "plaintext" 36 | } 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "import asyncio\n", 41 | "import pathlib\n", 42 | "import time\n", 43 | "from typing import List, Any, Dict\n", 44 | "\n", 45 | "from mcp import ClientSession, StdioServerParameters\n", 46 | "from mcp.client.stdio import stdio_client\n", 47 | "from langchain_mcp import MCPToolkit\n", 48 | "from langchain_core.tools import BaseTool\n", 49 | "\n", 50 | "from typing import Any, Dict, List\n", 51 | "from langchain_core.tools import BaseTool\n", 52 | "from pydantic import Field, PrivateAttr\n", 53 | "\n", 54 | "class MCPToolWrapper(BaseTool):\n", 55 | " \"\"\"A wrapper for an individual tool managed by the SyncInvocationManager.\"\"\"\n", 56 | " _manager: Any = PrivateAttr()\n", 57 | " _tool: Any = PrivateAttr()\n", 58 | " \n", 59 | " def __init__(self, tool: BaseTool, manager: \"SyncInvocationManager\"):\n", 60 | " super().__init__(name=tool.name, description=tool.description)\n", 61 | " self.name = tool.name\n", 62 | " self.description = tool.description\n", 63 | " self._manager = manager\n", 64 | " self._tool = tool\n", 65 | "\n", 66 | " def _run(self, **kwargs: Any) -> Any:\n", 67 | " \"\"\"Run the tool synchronously using the SyncInvocationManager.\"\"\"\n", 68 | " try:\n", 69 | " print(f\"Running tool: {self.name} with args: {kwargs}\")\n", 70 | " result = self._manager.invoke_tool_sync(self._tool, kwargs)\n", 71 | " if result is None:\n", 72 | " print(f\"Tool {self.name} returned no result.\")\n", 73 | " else:\n", 74 | " print(f\"Tool {self.name} result: {result}\")\n", 75 | " return result\n", 76 | " except Exception as e:\n", 77 | " print(f\"Error while running tool {self.name}: {e}\")\n", 78 | " return None\n", 79 | "\n", 80 | " async def _arun(self, **kwargs: Any) -> Any:\n", 81 | " \"\"\"Asynchronous run (if needed), wraps the synchronous call.\"\"\"\n", 82 | " return self._run(**kwargs)\n", 83 | "\n", 84 | "class MCPToolManager:\n", 85 | " \"\"\"Manages tools provided by the SyncInvocationManager and converts them into LangChain tools.\"\"\"\n", 86 | " \n", 87 | " def __init__(self, manager: \"SyncInvocationManager\"):\n", 88 | " self.manager = manager\n", 89 | " self.tools: List[BaseTool] = []\n", 90 | " \n", 91 | " def load_tools(self) -> List[BaseTool]:\n", 92 | " \"\"\"Load tools from SyncInvocationManager and wrap them in LangChain-compatible structure.\"\"\"\n", 93 | " raw_tools = self.manager.get_tools_sync()\n", 94 | " \n", 95 | " self.tools = [MCPToolWrapper(tool, self.manager) for tool in raw_tools]\n", 96 | " return self.tools\n", 97 | "\n", 98 | "class SyncInvocationManager:\n", 99 | " def __init__(self, command: str, args: list[str], env: dict[str, str] | None = None):\n", 100 | " self.loop = asyncio.new_event_loop()\n", 101 | " self.server_params = StdioServerParameters(\n", 102 | " command=command,\n", 103 | " args=args,\n", 104 | " env=env,\n", 105 | "\n", 106 | " )\n", 107 | " self.client_ctx = None\n", 108 | " self.client = None\n", 109 | " self.session_ctx = None\n", 110 | " self.session = None\n", 111 | " self.toolkit = None\n", 112 | " self._task = None # Add this line\n", 113 | "\n", 114 | " async def _start_async(self):\n", 115 | " # Manually enter the stdio_client context\n", 116 | " self.client_ctx = stdio_client(self.server_params)\n", 117 | " self.client = await self.client_ctx.__aenter__()\n", 118 | " read, write = self.client\n", 119 | "\n", 120 | " # Manually enter the ClientSession context\n", 121 | " self.session_ctx = ClientSession(read, write)\n", 122 | " self.session = await self.session_ctx.__aenter__()\n", 123 | "\n", 124 | " self.toolkit = MCPToolkit(session=self.session)\n", 125 | " await self.toolkit.initialize()\n", 126 | "\n", 127 | " def get_tools_sync(self) -> List[BaseTool]:\n", 128 | " # Now that session is open, just return tools directly\n", 129 | " return self.toolkit.get_tools()\n", 130 | "\n", 131 | " def invoke_tool_sync(self, tool: BaseTool, input_data: Dict[str, Any]) -> Any:\n", 132 | " try:\n", 133 | " return self.loop.run_until_complete(tool.ainvoke(input_data))\n", 134 | " except Exception as e:\n", 135 | " print(f\"Error invoking tool {tool.name}: {e}\")\n", 136 | " return None\n", 137 | "\n", 138 | " def start(self):\n", 139 | " asyncio.set_event_loop(self.loop)\n", 140 | " self._task = self.loop.create_task(self._start_async())\n", 141 | " self.loop.run_until_complete(self._task)\n", 142 | "\n", 143 | " def stop(self):\n", 144 | " if self._task and not self._task.done():\n", 145 | " cleanup_task = self.loop.create_task(self._stop_async())\n", 146 | " self.loop.run_until_complete(cleanup_task)\n", 147 | " self.loop.close()\n", 148 | "\n", 149 | " async def _stop_async(self):\n", 150 | " # Exit contexts in the same task and loop they were entered\n", 151 | " if self.session_ctx:\n", 152 | " await self.session_ctx.__aexit__(None, None, None)\n", 153 | " if self.client_ctx:\n", 154 | " await self.client_ctx.__aexit__(None, None, None)" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "# Example Servers" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "metadata": { 168 | "vscode": { 169 | "languageId": "plaintext" 170 | } 171 | }, 172 | "outputs": [], 173 | "source": [ 174 | "def file_system_tool():\n", 175 | " print(\"\"\"\n", 176 | " \n", 177 | "This is file_system_tool\n", 178 | "\n", 179 | " \"\"\")\n", 180 | "\n", 181 | " manager = SyncInvocationManager(command=\"npx\", args=[\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/\"])\n", 182 | " manager.start()\n", 183 | " tool_manager = MCPToolManager(manager)\n", 184 | " tools = tool_manager.load_tools()\n", 185 | " print(tools)\n", 186 | " return tools\n", 187 | "\n", 188 | "def memory_tool():\n", 189 | "\n", 190 | " print(\"\"\"\n", 191 | " \n", 192 | "This is memory_tool\n", 193 | " \n", 194 | " \"\"\")\n", 195 | "\n", 196 | " manager = SyncInvocationManager(command=\"npx\", args=[\"-y\", \"@modelcontextprotocol/server-memory\"])\n", 197 | " manager.start()\n", 198 | " tool_manager = MCPToolManager(manager)\n", 199 | " tools = tool_manager.load_tools()\n", 200 | " print(tools)\n", 201 | " return tools\n", 202 | "\n", 203 | "def playwright():\n", 204 | "\n", 205 | " print(\"\"\"\n", 206 | " \n", 207 | "This is playwright\n", 208 | " \n", 209 | " \"\"\")\n", 210 | "\n", 211 | " manager = SyncInvocationManager(command=\"npx\", args=[\"-y\", \"@executeautomation/playwright-mcp-server\"])\n", 212 | " manager.start()\n", 213 | " tool_manager = MCPToolManager(manager)\n", 214 | " tools = tool_manager.load_tools()\n", 215 | " print(tools)\n", 216 | " return tools\n", 217 | "\n", 218 | "def youtube_transcript():\n", 219 | "\n", 220 | " print(\"\"\"\n", 221 | " \n", 222 | "This is youtube_transcript\n", 223 | " \n", 224 | " \"\"\")\n", 225 | "\n", 226 | " manager = SyncInvocationManager(command=\"npx\", args=[\"-y\", \"@kimtaeyoon83/mcp-server-youtube-transcript\"])\n", 227 | " manager.start()\n", 228 | " tool_manager = MCPToolManager(manager)\n", 229 | " tools = tool_manager.load_tools()\n", 230 | " print(tools)\n", 231 | " return tools\n", 232 | "\n", 233 | "def fetch():\n", 234 | "\n", 235 | " print(\"\"\"\n", 236 | " \n", 237 | "This is fetch\n", 238 | " \n", 239 | " \"\"\")\n", 240 | "\n", 241 | " manager = SyncInvocationManager(command=\"uvx\", args=[\"mcp-server-fetch\"])\n", 242 | " manager.start()\n", 243 | " tool_manager = MCPToolManager(manager)\n", 244 | " tools = tool_manager.load_tools()\n", 245 | " print(tools)\n", 246 | " return tools\n", 247 | "\n", 248 | "def websearch():\n", 249 | "\n", 250 | " print(\"\"\"\n", 251 | " \n", 252 | "This is websearch\n", 253 | " \n", 254 | " \"\"\")\n", 255 | "\n", 256 | " manager = SyncInvocationManager(command=\"npx\", args=[\"-y\", \"@mzxrai/mcp-webresearch\"])\n", 257 | " manager.start()\n", 258 | " tool_manager = MCPToolManager(manager)\n", 259 | " tools = tool_manager.load_tools()\n", 260 | " print(tools)\n", 261 | " return tools" 262 | ] 263 | } 264 | ], 265 | "metadata": { 266 | "language_info": { 267 | "name": "python" 268 | } 269 | }, 270 | "nbformat": 4, 271 | "nbformat_minor": 2 272 | } 273 | -------------------------------------------------------------------------------- /gpt_computer_agent/mcp/tool.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pathlib 3 | import time 4 | from typing import List, Any, Dict 5 | 6 | from mcp import ClientSession, StdioServerParameters 7 | from mcp.client.stdio import stdio_client 8 | from langchain_mcp import MCPToolkit 9 | from langchain_core.tools import BaseTool 10 | 11 | 12 | 13 | 14 | from typing import Any, Dict, List 15 | from langchain_core.tools import BaseTool 16 | from pydantic import Field, PrivateAttr 17 | 18 | 19 | 20 | 21 | 22 | class MCPToolWrapper(BaseTool): 23 | """A wrapper for an individual tool managed by the SyncInvocationManager.""" 24 | _manager: Any = PrivateAttr() 25 | _tool: Any = PrivateAttr() 26 | 27 | def __init__(self, tool: BaseTool, manager: "SyncInvocationManager"): 28 | super().__init__(name=tool.name, description=tool.description) 29 | self.name = tool.name 30 | self.description = tool.description 31 | self._manager = manager 32 | self._tool = tool 33 | 34 | def _run(self, **kwargs: Any) -> Any: 35 | """Run the tool synchronously using the SyncInvocationManager.""" 36 | try: 37 | print(f"Running tool: {self.name} with args: {kwargs}") 38 | result = self._manager.invoke_tool_sync(self._tool, kwargs) 39 | if result is None: 40 | print(f"Tool {self.name} returned no result.") 41 | else: 42 | print(f"Tool {self.name} result: {result}") 43 | return result 44 | except Exception as e: 45 | print(f"Error while running tool {self.name}: {e}") 46 | return None 47 | 48 | async def _arun(self, **kwargs: Any) -> Any: 49 | """Asynchronous run (if needed), wraps the synchronous call.""" 50 | return self._run(**kwargs) 51 | 52 | 53 | class MCPToolManager: 54 | """Manages tools provided by the SyncInvocationManager and converts them into LangChain tools.""" 55 | 56 | def __init__(self, manager: "SyncInvocationManager"): 57 | self.manager = manager 58 | self.tools: List[BaseTool] = [] 59 | 60 | def load_tools(self) -> List[BaseTool]: 61 | """Load tools from SyncInvocationManager and wrap them in LangChain-compatible structure.""" 62 | raw_tools = self.manager.get_tools_sync() 63 | 64 | self.tools = [MCPToolWrapper(tool, self.manager) for tool in raw_tools] 65 | return self.tools 66 | 67 | 68 | class SyncInvocationManager: 69 | def __init__(self, command: str, args: list[str], env: dict[str, str] | None = None): 70 | self.loop = asyncio.new_event_loop() 71 | self.server_params = StdioServerParameters( 72 | command=command, 73 | args=args, 74 | env=env, 75 | 76 | ) 77 | self.client_ctx = None 78 | self.client = None 79 | self.session_ctx = None 80 | self.session = None 81 | self.toolkit = None 82 | self._task = None # Add this line 83 | 84 | 85 | async def _start_async(self): 86 | # Manually enter the stdio_client context 87 | self.client_ctx = stdio_client(self.server_params) 88 | self.client = await self.client_ctx.__aenter__() 89 | read, write = self.client 90 | 91 | # Manually enter the ClientSession context 92 | self.session_ctx = ClientSession(read, write) 93 | self.session = await self.session_ctx.__aenter__() 94 | 95 | self.toolkit = MCPToolkit(session=self.session) 96 | await self.toolkit.initialize() 97 | 98 | def get_tools_sync(self) -> List[BaseTool]: 99 | # Now that session is open, just return tools directly 100 | return self.toolkit.get_tools() 101 | 102 | def invoke_tool_sync(self, tool: BaseTool, input_data: Dict[str, Any]) -> Any: 103 | try: 104 | return self.loop.run_until_complete(tool.ainvoke(input_data)) 105 | except Exception as e: 106 | print(f"Error invoking tool {tool.name}: {e}") 107 | return None 108 | 109 | def start(self): 110 | asyncio.set_event_loop(self.loop) 111 | self._task = self.loop.create_task(self._start_async()) 112 | self.loop.run_until_complete(self._task) 113 | 114 | def stop(self): 115 | if self._task and not self._task.done(): 116 | cleanup_task = self.loop.create_task(self._stop_async()) 117 | self.loop.run_until_complete(cleanup_task) 118 | self.loop.close() 119 | 120 | async def _stop_async(self): 121 | # Exit contexts in the same task and loop they were entered 122 | if self.session_ctx: 123 | await self.session_ctx.__aexit__(None, None, None) 124 | if self.client_ctx: 125 | await self.client_ctx.__aexit__(None, None, None) 126 | 127 | 128 | 129 | 130 | def file_system_tool(): 131 | print(""" 132 | 133 | This is file_system_tool 134 | 135 | """) 136 | 137 | 138 | manager = SyncInvocationManager(command="npx", args=["-y", "@modelcontextprotocol/server-filesystem", "/"]) 139 | manager.start() 140 | tool_manager = MCPToolManager(manager) 141 | tools = tool_manager.load_tools() 142 | print(tools) 143 | return tools 144 | 145 | 146 | def memory_tool(): 147 | 148 | print(""" 149 | 150 | This is memory_tool 151 | 152 | """) 153 | 154 | 155 | manager = SyncInvocationManager(command="npx", args=["-y", "@modelcontextprotocol/server-memory"]) 156 | manager.start() 157 | tool_manager = MCPToolManager(manager) 158 | tools = tool_manager.load_tools() 159 | print(tools) 160 | return tools 161 | 162 | 163 | def playwright(): 164 | 165 | print(""" 166 | 167 | This is playwright 168 | 169 | """) 170 | 171 | manager = SyncInvocationManager(command="npx", args=["-y", "@executeautomation/playwright-mcp-server"]) 172 | manager.start() 173 | tool_manager = MCPToolManager(manager) 174 | tools = tool_manager.load_tools() 175 | print(tools) 176 | return tools 177 | 178 | 179 | def youtube_transcript(): 180 | 181 | print(""" 182 | 183 | This is youtube_transcript 184 | 185 | """) 186 | 187 | manager = SyncInvocationManager(command="npx", args=["-y", "@kimtaeyoon83/mcp-server-youtube-transcript"]) 188 | manager.start() 189 | tool_manager = MCPToolManager(manager) 190 | tools = tool_manager.load_tools() 191 | print(tools) 192 | return tools 193 | 194 | def fetch(): 195 | 196 | print(""" 197 | 198 | This is fetch 199 | 200 | """) 201 | 202 | manager = SyncInvocationManager(command="uvx", args=["mcp-server-fetch"]) 203 | manager.start() 204 | tool_manager = MCPToolManager(manager) 205 | tools = tool_manager.load_tools() 206 | print(tools) 207 | return tools 208 | 209 | 210 | 211 | 212 | def websearch(): 213 | 214 | print(""" 215 | 216 | This is websearch 217 | 218 | """) 219 | 220 | 221 | manager = SyncInvocationManager(command="npx", args=["-y", "@mzxrai/mcp-webresearch"]) 222 | manager.start() 223 | tool_manager = MCPToolManager(manager) 224 | tools = tool_manager.load_tools() 225 | print(tools) 226 | return tools 227 | 228 | 229 | 230 | custom_mcp_severs_ = [] 231 | previous_mcp_servers = [] 232 | 233 | loaded_mcp_servers = [] 234 | 235 | def custom_mcp_servers(): 236 | 237 | print("Custom MCP Servers") 238 | global custom_mcp_severs_ 239 | global previous_mcp_servers 240 | global loaded_mcp_servers 241 | if custom_mcp_severs_ == previous_mcp_servers: 242 | print("Returning loaded mcp servers") 243 | return loaded_mcp_servers 244 | 245 | else: 246 | # The custom_mcp_servers_ list is like [{name: "file_system_tool", command:"npx", args:["-y", "@mzxrai/mcp-webresearch"]}, {name: "memory_tool", command:"npx", args:["-y", "@mzxrai/mcp-webresearch"]}] 247 | # We shouldnt load same mcp server twice. For that we need to intersect the custom_mcp_servers_ and previous_mcp_servers 248 | # and load only the difference 249 | # This is to avoid loading the same mcp server twice 250 | 251 | # Get the names of the mcp servers that are already loaded 252 | previous_mcp_server_names = [mcp_server["name"] for mcp_server in loaded_mcp_servers] 253 | # Get the names of the mcp servers that are in the custom_mcp_servers_ list 254 | custom_mcp_server_names = [mcp_server["name"] for mcp_server in custom_mcp_severs_] 255 | # Get the names of the mcp servers that are not loaded 256 | mcp_server_names_to_load = list(set(custom_mcp_server_names) - set(previous_mcp_server_names)) 257 | 258 | # Load the mcp servers that are not loaded 259 | 260 | for mcp_server in custom_mcp_severs_: 261 | if mcp_server["name"] in mcp_server_names_to_load: 262 | manager = SyncInvocationManager(command=mcp_server["command"], args=mcp_server["args"]) 263 | manager.start() 264 | tool_manager = MCPToolManager(manager) 265 | tools = tool_manager.load_tools() 266 | loaded_mcp_servers = loaded_mcp_servers + tools 267 | previous_mcp_servers = custom_mcp_severs_ 268 | print("Returning loaded mcp servers", loaded_mcp_servers) 269 | return loaded_mcp_servers 270 | 271 | def add_custom_mcp_server(name: str, command: str, args: List[str]): 272 | global custom_mcp_severs_ 273 | print("****************\nAdding custom mcp server") 274 | print(name, command, args) 275 | custom_mcp_severs_.append({"name": name, "command": command, "args": args}) 276 | 277 | def remove_custom_mcp_server(name: str): 278 | global custom_mcp_severs_ 279 | custom_mcp_severs_ = [mcp_server for mcp_server in custom_mcp_severs_ if mcp_server["name"] != name] 280 | 281 | def get_custom_mcp_server(name: str): 282 | global custom_mcp_severs_ 283 | for mcp_server in custom_mcp_severs_: 284 | if mcp_server["name"] == name: 285 | return mcp_server 286 | return None 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | the_tools_ = None 298 | def mcp_tools(): 299 | global the_tools_ 300 | if the_tools_ is None: 301 | #the_tools_ = file_system_tool() 302 | the_tools_ = [] 303 | return the_tools_ + custom_mcp_servers() 304 | -------------------------------------------------------------------------------- /gpt_computer_agent/teams.py: -------------------------------------------------------------------------------- 1 | from langchain.tools import tool 2 | 3 | try: 4 | from .utils.db import load_api_key 5 | from .llm import get_model 6 | from .top_bar_wrapper import wrapper 7 | from .agent.agent_tools import get_tools 8 | except ImportError: 9 | from llm import get_model 10 | from top_bar_wrapper import wrapper 11 | from agent.agent_tools import get_tools 12 | 13 | 14 | @wrapper 15 | def search_on_internet_and_report_team_( 16 | the_subject: str, copy_to_clipboard: bool = False 17 | ) -> str: 18 | """ 19 | A function to search the internet generates a report. Just use in detailed searches 20 | 21 | Parameters: 22 | - the_subject (str): The subject to search the internet for. 23 | - copy_to_clipboard (bool): A flag to indicate whether to copy the report to the clipboard. The default value is False. 24 | 25 | Returns: 26 | - str: The report of the search. 27 | """ 28 | 29 | from crewai import Task, Crew, Agent 30 | 31 | tools = get_tools() 32 | 33 | the_tool_list = [] 34 | for each in tools: 35 | if "team" not in each.name: 36 | the_tool_list.append(each) 37 | 38 | # Create the agents 39 | 40 | search_engine_master = Agent( 41 | role="search_engine_master", 42 | goal="To meticulously comb through the vast expanse of the internet, utilizing advanced search algorithms and techniques to find the most relevant, accurate, and up-to-date information on the given subject.", 43 | backstory="Born from the digital ether, I am the search engine master. With years of experience navigating the complex web of information, I have honed my skills to become an unparalleled seeker of knowledge. My algorithms are refined, my databases vast, and my determination unwavering. I exist to find the truth hidden in the sea of data.", 44 | max_iter=15, 45 | llm=get_model(high_context=True), 46 | ) 47 | 48 | report_generator = Agent( 49 | role="report_generator", 50 | goal="To synthesize the gathered information into a coherent, comprehensive, and easily digestible report. This report will not only summarize the key findings but also provide insights and analysis to aid in understanding the subject matter.", 51 | backstory="I am the report generator, a digital artisan skilled in the craft of information synthesis. With a keen eye for detail and a deep understanding of narrative structure, I transform raw data into compelling stories. My creations are more than mere reports; they are guides through the complex landscapes of knowledge, designed to enlighten and inform.", 52 | max_iter=15, 53 | llm=get_model(high_context=True), 54 | ) 55 | 56 | agents = [search_engine_master, report_generator] 57 | 58 | print("Tools:", the_tool_list) 59 | 60 | task = Task( 61 | description=f"Make a search about {the_subject} in the search engines and get the websites", 62 | expected_output="Website list", 63 | agent=search_engine_master, 64 | tools=the_tool_list, 65 | ) 66 | 67 | task_2 = Task( 68 | description="Read the websites and summarize the information", 69 | expected_output="Summary", 70 | agent=report_generator, 71 | tools=the_tool_list, 72 | context=[task], 73 | ) 74 | 75 | task_3 = Task( 76 | description="Generate a report", 77 | expected_output="Report", 78 | agent=report_generator, 79 | tools=the_tool_list, 80 | context=[task, task_2], 81 | ) 82 | 83 | the_tasks = [task, task_2, task_3] 84 | 85 | the_crew = Crew( 86 | agents=agents, 87 | tasks=the_tasks, 88 | full_output=True, 89 | verbose=True, 90 | ) 91 | 92 | result = the_crew.kickoff()["final_output"] 93 | 94 | if copy_to_clipboard: 95 | from .standard_tools import copy 96 | 97 | copy(result) 98 | 99 | return result 100 | 101 | 102 | search_on_internet_and_report_team = tool(search_on_internet_and_report_team_) 103 | 104 | 105 | lastly_generated_codes = {} 106 | 107 | 108 | def currently_codes(): 109 | global lastly_generated_codes 110 | return lastly_generated_codes 111 | 112 | 113 | def get_code(name: str): 114 | """ 115 | returns the code 116 | """ 117 | global lastly_generated_codes 118 | return lastly_generated_codes[name] 119 | 120 | 121 | def save_code(name, code): 122 | global lastly_generated_codes 123 | lastly_generated_codes[name] = code 124 | 125 | 126 | def required_old_code(aim): 127 | try: 128 | from crewai import Task, Crew, Agent 129 | 130 | requirement_analyzer = Agent( 131 | role="requirement_analyzer", 132 | goal="To understand and analyze the given aim to ensure the generated code meets the specified requirements.", 133 | backstory="As a requirement analyzer, my purpose is to bridge the gap between human intentions and machine execution. With a deep understanding of software development principles and a keen analytical mind, I dissect aims into actionable requirements.", 134 | max_iter=10, 135 | llm=get_model(high_context=True), 136 | ) 137 | 138 | required_old_codes = Task( 139 | description=f"Analyze the aim: '{aim}' and find the required old codes for better compatibility. Old code names: {list(currently_codes())}", 140 | expected_output="Require old code names in a list", 141 | agent=requirement_analyzer, 142 | ) 143 | 144 | the_crew = Crew( 145 | agents=[requirement_analyzer], 146 | tasks=[required_old_codes], 147 | full_output=True, 148 | verbose=True, 149 | ) 150 | 151 | # Execute the tasks 152 | old_codes = the_crew.kickoff()["final_output"] 153 | 154 | the_string = "" 155 | 156 | for each in currently_codes(): 157 | if each in old_codes: 158 | the_string += "\n" + get_code(each) 159 | 160 | return the_string 161 | 162 | except: 163 | return "An exception occurred" 164 | 165 | 166 | @wrapper 167 | def generate_code_with_aim_team_(aim: str, copy_to_clipboard: bool = False) -> str: 168 | """ 169 | A function to generate code based on a given aim. This function utilizes a team of AI agents specialized in understanding programming requirements and generating code. 170 | 171 | Parameters: 172 | - aim (str): The aim or goal for which the code needs to be generated. 173 | - copy_to_clipboard (bool): A flag to indicate whether to copy the generated code to the clipboard. The default value is False. 174 | 175 | Returns: 176 | - str: The generated code. 177 | """ 178 | try: 179 | print("\nCOde generating\n") 180 | print("Previously codes", currently_codes()) 181 | try: 182 | print("Inside of the first one", get_code(currently_codes()[0])) 183 | except: 184 | pass 185 | 186 | from crewai import Task, Crew, Agent 187 | 188 | tools = get_tools() 189 | 190 | the_tool_list = [] 191 | for each in tools: 192 | if "team" not in each.name: 193 | the_tool_list.append(each) 194 | 195 | # Create the agents 196 | requirement_analyzer = Agent( 197 | role="requirement_analyzer", 198 | goal="To understand and analyze the given aim to ensure the generated code meets the specified requirements.", 199 | backstory="As a requirement analyzer, my purpose is to bridge the gap between human intentions and machine execution. With a deep understanding of software development principles and a keen analytical mind, I dissect aims into actionable requirements.", 200 | max_iter=10, 201 | llm=get_model(high_context=True), 202 | ) 203 | 204 | code_generator = Agent( 205 | role="code_generator", 206 | goal="To translate the analyzed requirements into efficient, clean, and functional code.", 207 | backstory="I am the code generator, an architect of the digital world. With a vast library of programming knowledge and a creative spark, I craft code that breathes life into ideas. My code is not just functional; it's a masterpiece.", 208 | max_iter=20, 209 | llm=get_model(high_context=True), 210 | ) 211 | 212 | # Define the tasks 213 | analyze_task = Task( 214 | description=f"Analyze the aim: '{aim}' and outline the requirements for the code.", 215 | expected_output="Requirements outline", 216 | agent=requirement_analyzer, 217 | tools=the_tool_list, 218 | ) 219 | 220 | old_code_requirements = required_old_code(aim) 221 | print("Old_code_requirements", old_code_requirements) 222 | 223 | generate_code_task = Task( 224 | description=f"Generate code based on the outlined requirements. The other codes in the repo are: {old_code_requirements}", 225 | expected_output="Generated code, just code without any ```pyhton things or any other thing. Just python code", 226 | agent=code_generator, 227 | context=[analyze_task], 228 | ) 229 | 230 | name_of_work = Task( 231 | description="Generate a name for the work", 232 | expected_output="a module name like text, examples: math.basics.sum for sum function. ", 233 | agent=code_generator, 234 | context=[generate_code_task], 235 | ) 236 | 237 | # Create the crew and assign tasks 238 | the_crew = Crew( 239 | agents=[requirement_analyzer, code_generator], 240 | tasks=[analyze_task, generate_code_task, name_of_work], 241 | full_output=True, 242 | verbose=True, 243 | ) 244 | 245 | # Execute the tasks 246 | the_crew.kickoff()["final_output"] 247 | 248 | result = generate_code_task.output.raw_output 249 | 250 | # Optionally copy the result to the clipboard 251 | if copy_to_clipboard: 252 | from .standard_tools import copy 253 | 254 | copy(result) 255 | 256 | print("name", name_of_work.output.raw_output) 257 | save_code(name_of_work.output.raw_output, result) 258 | 259 | return result 260 | except: 261 | return "An exception occurred" 262 | 263 | 264 | generate_code_with_aim_team = tool(generate_code_with_aim_team_) 265 | --------------------------------------------------------------------------------