Skip to content

Commit

Permalink
Merge pull request #1482 from leetwito/feat-dynamic-computer-tools-list
Browse files Browse the repository at this point in the history
Feat dynamic computer tools list
  • Loading branch information
KillianLucas authored Oct 21, 2024
2 parents 03bbfdb + 4049913 commit fffb575
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,9 @@ misc/

# Ignore litellm_uuid.txt
litellm_uuid.txt

# some more
.aider*
file.txt
numbers.txt
poetry.lock
21 changes: 15 additions & 6 deletions interpreter/core/computer/browser/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def __init__(self, computer):
self._driver = None

@property
def driver(self):
def driver(self, headless=False):
if self._driver is None:
self.setup()
self.setup(headless)
return self._driver

@driver.setter
Expand Down Expand Up @@ -62,10 +62,19 @@ def fast_search(self, query):

return response.json()["result"]

def setup(self):
self.service = Service(ChromeDriverManager().install())
self.options = webdriver.ChromeOptions()
self._driver = webdriver.Chrome(service=self.service, options=self.options)
def setup(self, headless):
try:
self.service = Service(ChromeDriverManager().install())
self.options = webdriver.ChromeOptions()
# Run Chrome in headless mode
if headless:
self.options.add_argument("--headless")
self.options.add_argument("--disable-gpu")
self.options.add_argument("--no-sandbox")
self._driver = webdriver.Chrome(service=self.service, options=self.options)
except Exception as e:
print(f"An error occurred while setting up the WebDriver: {e}")
self._driver = None

def go_to_url(self, url):
"""Navigate to a URL"""
Expand Down
88 changes: 76 additions & 12 deletions interpreter/core/computer/computer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import inspect

from .ai.ai import Ai
from .browser.browser import Browser
Expand Down Expand Up @@ -57,24 +58,14 @@ def __init__(self, interpreter):
self.interpreter.max_output
) # Should mirror interpreter.max_output

self.system_message = """
self.system_message = f"""
# THE COMPUTER API
A python `computer` module is ALREADY IMPORTED, and can be used for many tasks:
```python
computer.browser.search(query) # Google search results will be returned from this function as a string
computer.files.edit(path_to_file, original_text, replacement_text) # Edit a file
computer.calendar.create_event(title="Meeting", start_date=datetime.datetime.now(), end_date=datetime.datetime.now() + datetime.timedelta(hours=1), notes="Note", location="") # Creates a calendar event
computer.calendar.get_events(start_date=datetime.date.today(), end_date=None) # Get events between dates. If end_date is None, only gets events for start_date
computer.calendar.delete_event(event_title="Meeting", start_date=datetime.datetime) # Delete a specific event with a matching title and start date, you may need to get use get_events() to find the specific event object first
computer.contacts.get_phone_number("John Doe")
computer.contacts.get_email_address("John Doe")
computer.mail.send("[email protected]", "Meeting Reminder", "Reminder that our meeting is at 3pm today.", ["path/to/attachment.pdf", "path/to/attachment2.pdf"]) # Send an email with a optional attachments
computer.mail.get(4, unread=True) # Returns the [number] of unread emails, or all emails if False is passed
computer.mail.unread_count() # Returns the number of unread emails
computer.sms.send("555-123-4567", "Hello from the computer!") # Send a text message. MUST be a phone number, so use computer.contacts.get_phone_number frequently here
{"\n".join(self._get_all_computer_tools_signature_and_description())}
```
Do not import the computer module, or any of its sub-modules. They are already imported.
Expand All @@ -90,6 +81,79 @@ def languages(self):
def languages(self, value):
self.terminal.languages = value

def _get_all_computer_tools_list(self):
return [self.mouse, self.keyboard, self.display, self.clipboard, self.mail, self.sms, self.calendar, self.contacts, self.browser, self.os, self.vision, self.skills, self.docs, self.ai, self.files]

def _get_all_computer_tools_signature_and_description(self):
"""
This function returns a list of all the computer tools that are available with their signature and description from the function docstrings.
for example:
computer.browser.search(query) # Searches the web for the specified query and returns the results.
computer.calendar.create_event(title: str, start_date: datetime.datetime, end_date: datetime.datetime, location: str = "", notes: str = "", calendar: str = None) -> str # Creates a new calendar event in the default calendar with the given parameters using AppleScript.
"""
tools = self._get_all_computer_tools_list()
tools_signature_and_description = []
for tool in tools:
tool_info = self._extract_tool_info(tool)
for method in tool_info["methods"]:
# Format as tool_signature # tool_description
formatted_info = f"{method['signature']} # {method['description']}"
tools_signature_and_description.append(formatted_info)
return tools_signature_and_description

def _extract_tool_info(self, tool):
"""
Helper function to extract the signature and description of a tool's methods.
"""
tool_info = {
"signature": tool.__class__.__name__,
"methods": []
}
if tool.__class__.__name__ == "Browser":
methods = []
for name in dir(tool):
if 'driver' in name:
continue # Skip methods containing 'driver' in their name
attr = getattr(tool, name)
if callable(attr) and not name.startswith('_') and not hasattr(attr, '__wrapped__') and not isinstance(attr, property):
# Construct the method signature manually
param_str = ", ".join(
param for param in attr.__code__.co_varnames[:attr.__code__.co_argcount]
)
full_signature = f"computer.{tool.__class__.__name__.lower()}.{name}({param_str})"
# Get the method description
method_description = attr.__doc__ or ""
# Append the method details
tool_info["methods"].append({
"signature": full_signature,
"description": method_description.strip()
})
return tool_info

for name, method in inspect.getmembers(tool, predicate=inspect.ismethod):
# Check if the method should be ignored based on its decorator
if not name.startswith("_") and not hasattr(method, '__wrapped__'):
# Get the method signature
method_signature = inspect.signature(method)
# Construct the signature string without *args and **kwargs
param_str = ", ".join(
f"{param.name}"
if param.default == param.empty
else f"{param.name}={param.default!r}"
for param in method_signature.parameters.values()
if param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD)
)
full_signature = f"computer.{tool.__class__.__name__.lower()}.{name}({param_str})"
# Get the method description
method_description = method.__doc__ or ""
# Append the method details
tool_info["methods"].append({
"signature": full_signature,
"description": method_description.strip()
})
return tool_info


def run(self, *args, **kwargs):
"""
Shortcut for computer.terminal.run
Expand Down
17 changes: 11 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ isort = "^5.12.0"
pre-commit = "^3.5.0"
pytest = "^7.4.0"
sniffio = "^1.3.0"
websockets = "^13.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
26 changes: 26 additions & 0 deletions tests/core/computer/test_computer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
from unittest import mock
from interpreter.core.computer.computer import Computer

class TestComputer(unittest.TestCase):
def setUp(self):
self.computer = Computer(mock.Mock())

def test_get_all_computer_tools_list(self):
# Act
tools_list = self.computer._get_all_computer_tools_list()

# Assert
self.assertEqual(len(tools_list), 15)

def test_get_all_computer_tools_signature_and_description(self):
# Act
tools_description = self.computer._get_all_computer_tools_signature_and_description()

# Assert
self.assertGreater(len(tools_description), 64)

if __name__ == "__main__":
testing = TestComputer()
testing.setUp()
testing.test_get_all_computer_tools_signature_and_description()

0 comments on commit fffb575

Please sign in to comment.