diff --git a/CREDITS.md b/CREDITS.md index 2bc97704a73..873742b7e01 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -2,7 +2,7 @@ ## Contributors -We would like to thank all the [contributors](https://github.com/All-Hands-AI/OpenHands/graphs/contributors) who have helped make OpenHands possible. Your dedication and hard work are greatly appreciated. +We would like to thank all the [contributors](https://github.com/All-Hands-AI/OpenHands/graphs/contributors) who have helped make OpenHands possible. We greatly appreciate your dedication and hard work. ## Open Source Projects @@ -10,7 +10,7 @@ OpenHands includes and adapts the following open source projects. We are gratefu #### [SWE Agent](https://github.com/princeton-nlp/swe-agent) - License: MIT License - - Description: Adapted for use in OpenHands's agenthub + - Description: Adapted for use in OpenHands's agent hub #### [Aider](https://github.com/paul-gauthier/aider) - License: Apache License 2.0 diff --git a/README.md b/README.md index 93b0643285f..353eb654f4b 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or jump to the [ The easiest way to run OpenHands is in Docker. You can change `WORKSPACE_BASE` below to point OpenHands to existing code that you'd like to modify. -See the [Getting Started](https://docs.all-hands.dev/modules/usage/getting-started) guide for +See the [Installation](https://docs.all-hands.dev/modules/usage/installation) guide for system requirements and more information. ```bash @@ -124,7 +124,7 @@ You'll need a model provider and API key. One option that works well: [Claude 3. You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), or as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode). -Visit [Getting Started](https://docs.all-hands.dev/modules/usage/getting-started) for more information and setup instructions. +Visit [Installation](https://docs.all-hands.dev/modules/usage/installation) for more information and setup instructions. If you want to modify the Kevin source code, check out [Development.md](https://github.com/SmartManoj/Kevin/blob/main/Development.md). diff --git a/agenthub/browsing_agent/response_parser.py b/agenthub/browsing_agent/response_parser.py index 831e32b4bff..1561cebf152 100644 --- a/agenthub/browsing_agent/response_parser.py +++ b/agenthub/browsing_agent/response_parser.py @@ -81,17 +81,17 @@ def parse(self, action_str: str) -> Action: assert ( self.action_str is not None ), 'self.action_str should not be None when parse is called' - action_cmd = self.action_str.group(1).strip() + browser_actions = self.action_str.group(1).strip() thought = action_str.replace(self.action_str.group(0), '').strip() msg_content = '' - for sub_action in action_cmd.split('\n'): + for sub_action in browser_actions.split('\n'): if 'send_msg_to_user(' in sub_action: tree = ast.parse(sub_action) args = tree.body[0].value.args # type: ignore msg_content = args[0].value return BrowseInteractiveAction( - browser_actions=action_cmd, + browser_actions=browser_actions, thought=thought, browsergym_send_msg_to_user=msg_content, ) diff --git a/docs/modules/usage/getting-started.mdx b/docs/modules/usage/getting-started.mdx index 37d11c8090c..d2cebc5c2de 100644 --- a/docs/modules/usage/getting-started.mdx +++ b/docs/modules/usage/getting-started.mdx @@ -9,28 +9,30 @@ engineering tasks without any guidance. So it's important to get a feel for what does well, and where it might need some help. ## Hello World + The first thing you might want to try is a simple "hello world" example. This can be more complicated than it sounds! Try prompting the agent with: > Please write a bash script hello.sh that prints "hello world!" -You should see that the agent not only writes the script--it sets the correct +You should see that the agent not only writes the script, it sets the correct permissions and runs the script to check the output. You can continue prompting the agent to refine your code. This is a great way to -work with agents--start simple, and iterate. +work with agents. Start simple, and iterate. > Please modify hello.sh so that it accepts a name as the first argument, but defaults to "world" -You can also work in any language you need--though the agent might need to spend some +You can also work in any language you need, though the agent might need to spend some time setting up its environment! > Please convert hello.sh to a Ruby script, and run it -## Building from scratch -Agents do exceptionally well at "greenfield" tasks--tasks where they don't need -any context about an existing codebase, and they can just start from scratch. +## Building From Scratch + +Agents do exceptionally well at "greenfield" tasks (tasks where they don't need +any context about an existing codebase) and they can just start from scratch. It's best to start with a simple task, and then iterate on it. It's also best to be as specific as possible about what you want, what the tech stack should be, etc. @@ -51,7 +53,8 @@ You can ask the agent to commit and push for you: > Please commit the changes and push them to a new branch called "feature/due-dates" -## Adding new code +## Adding New Code + OpenHands can also do a great job adding new code to an existing code base. For example, you can ask OpenHands to add a new GitHub action to your project @@ -70,6 +73,7 @@ and more accurately. And it'll cost you fewer tokens! > directory. It should use the existing Widget component. ## Refactoring + OpenHands does great at refactoring existing code, especially in small chunks. You probably don't want to try rearchitecting your whole codebase, but breaking up long files and functions, renaming variables, etc. tend to work very well. @@ -81,6 +85,7 @@ long files and functions, renaming variables, etc. tend to work very well. > Please break ./api/routes.js into separate files for each route ## Bug Fixes + OpenHands can also help you track down and fix bugs in your code. But, as any developer knows, bug fixing can be extremely tricky, and often OpenHands will need more context. It helps if you've diagnosed the bug, but want OpenHands to figure out the logic. @@ -95,6 +100,7 @@ You can ask the agent to write a new test, and then iterate until it fixes the b > The `hello` function crashes on the empty string. Please write a test that reproduces this bug, then fix the code so it passes. ## More + OpenHands is capable of helping out on just about any coding task. But it takes some practice to get the most out of it. Remember to: * Keep your tasks small diff --git a/openhands/llm/async_llm.py b/openhands/llm/async_llm.py index ad271605b06..b1a83a4ac9c 100644 --- a/openhands/llm/async_llm.py +++ b/openhands/llm/async_llm.py @@ -1,7 +1,7 @@ import asyncio from functools import partial -from litellm import completion as litellm_acompletion +from litellm import acompletion as litellm_acompletion from openhands.core.exceptions import UserCancelledError from openhands.core.logger import openhands_logger as logger @@ -40,7 +40,7 @@ def __init__(self, *args, **kwargs): retry_multiplier=self.config.retry_multiplier, ) async def async_completion_wrapper(*args, **kwargs): - """Wrapper for the litellm acompletion function.""" + """Wrapper for the litellm acompletion function that adds logging and cost tracking.""" messages: list[Message] | Message = [] # some callers might send the model and messages directly @@ -84,6 +84,8 @@ async def check_stopped(): message_back = resp['choices'][0]['message']['content'] self.log_response(message_back) + + # log costs and tokens used self._post_completion(resp) # We do not support streaming in this method, thus return resp diff --git a/openhands/llm/llm.py b/openhands/llm/llm.py index d8f20afc315..490d0e8de53 100644 --- a/openhands/llm/llm.py +++ b/openhands/llm/llm.py @@ -40,6 +40,8 @@ # tuple of exceptions to retry on LLM_RETRY_EXCEPTIONS: tuple[type[Exception], ...] = ( APIConnectionError, + # FIXME: APIError is useful on 502 from a proxy for example, + # but it also retries on other errors that are permanent APIError, InternalServerError, RateLimitError, diff --git a/tests/unit/test_browsing_agent_parser.py b/tests/unit/test_browsing_agent_parser.py new file mode 100644 index 00000000000..bc1372611c2 --- /dev/null +++ b/tests/unit/test_browsing_agent_parser.py @@ -0,0 +1,54 @@ +import pytest + +from agenthub.browsing_agent.response_parser import ( + BrowseInteractiveAction, + BrowsingResponseParser, +) + + +@pytest.mark.parametrize( + 'action_str, expected', + [ + ("click('81'", "click('81')```"), + ( + '"We need to search the internet\n```goto("google.com")', + '"We need to search the internet\n```goto("google.com"))```', + ), + ("```click('81'", "```click('81')```"), + ("click('81')", "click('81'))```"), + ], +) +def test_parse_response(action_str: str, expected: str) -> None: + # BrowsingResponseParser.parse_response + parser = BrowsingResponseParser() + response = {'choices': [{'message': {'content': action_str}}]} + result = parser.parse_response(response) + assert result == expected + + +@pytest.mark.parametrize( + 'action_str, expected_browser_actions, expected_thought, expected_msg_content', + [ + ("click('81')```", "click('81')", '', ''), + ("```click('81')```", "click('81')", '', ''), + ( + "We need to perform a click\n```click('81')", + "click('81')", + 'We need to perform a click', + '', + ), + ], +) +def test_parse_action( + action_str: str, + expected_browser_actions: str, + expected_thought: str, + expected_msg_content: str, +) -> None: + # BrowsingResponseParser.parse_action + parser = BrowsingResponseParser() + action = parser.parse_action(action_str) + assert isinstance(action, BrowseInteractiveAction) + assert action.browser_actions == expected_browser_actions + assert action.thought == expected_thought + assert action.browsergym_send_msg_to_user == expected_msg_content