Ver código fonte

Introduce TypoFixerAgent for in-place typo corrections in agenthub/micro (#1613)

* Add TypoFixerAgent micro-agent to fix typos

* Improve parse_response to accurately extract the first complete JSON object

* Add tests for parse_response function handling complex scenarios

* Fix tests and logic to use action_from_dict

* Fix small formatting issues
Aleksandar 1 ano atrás
pai
commit
4bf4119259

+ 20 - 11
agenthub/micro/agent.py

@@ -14,17 +14,26 @@ from .registry import all_microagents
 
 
 def parse_response(orig_response: str) -> Action:
-    json_start = orig_response.find('{')
-    json_end = orig_response.rfind('}') + 1
-    response = orig_response[json_start:json_end]
-    try:
-        action_dict = json.loads(response)
-    except json.JSONDecodeError as e:
-        raise LLMOutputError(
-            'Invalid JSON in response. Please make sure the response is a valid JSON object'
-        ) from e
-    action = action_from_dict(action_dict)
-    return action
+    depth = 0
+    start = -1
+    for i, char in enumerate(orig_response):
+        if char == '{':
+            if depth == 0:
+                start = i
+            depth += 1
+        elif char == '}':
+            depth -= 1
+            if depth == 0 and start != -1:
+                response = orig_response[start : i + 1]
+                try:
+                    action_dict = json.loads(response)
+                    action = action_from_dict(action_dict)
+                    return action
+                except json.JSONDecodeError as e:
+                    raise LLMOutputError(
+                        'Invalid JSON in response. Please make sure the response is a valid JSON object.'
+                    ) from e
+    raise LLMOutputError('No valid JSON object found in response.')
 
 
 def my_encoder(obj):

+ 5 - 0
agenthub/micro/typo_fixer_agent/agent.yaml

@@ -0,0 +1,5 @@
+name: TypoFixerAgent
+description: Fixes typos in files in the current working directory
+inputs: {}
+outputs:
+  summary: string

+ 46 - 0
agenthub/micro/typo_fixer_agent/prompt.md

@@ -0,0 +1,46 @@
+# Task
+You are a proofreader tasked with fixing typos in the files in your current working directory. Your goal is to:
+1. Scan the files for typos
+2. Overwrite the files with the typos fixed
+3. Provide a summary of the typos fixed
+
+## Available Actions
+{{ instructions.actions.read }}
+{{ instructions.actions.write }}
+{{ instructions.actions.run }}
+{{ instructions.actions.think }}
+{{ instructions.actions.finish }}
+
+To complete this task:
+1. Use the `read` action to read the contents of the files in your current working directory. Make sure to provide the file path in the format `'./file_name.ext'`.
+2. Use the `think` action to analyze the contents and identify typos.
+3. Use the `write` action to create new versions of the files with the typos fixed.
+  - Overwrite the original files with the corrected content. Make sure to provide the file path in the format `'./file_name.ext'`.
+4. Use the `think` action to generate a summary of the typos fixed, including the original and fixed versions of each typo, and the file(s) they were found in.
+5. Use the `finish` action to return the summary in the `outputs.summary` field.
+
+Do NOT finish until you have fixed all the typos and generated a summary.
+
+## History
+{{ instructions.history_truncated }}
+{{ to_json(state.history[-5:]) }}
+
+## Format
+{{ instructions.format.action }}
+
+For example, if you want to use the read action to read the contents of a file named example.txt, your response should look like this:
+{
+  "action": "read",
+  "args": {
+    "path": "./example.txt"
+  }
+}
+
+Similarly, if you want to use the write action to write content to a file named output.txt, your response should look like this:
+{
+  "action": "write",
+  "args": {
+    "path": "./output.txt",
+    "content": "This is the content to be written to the file."
+  }
+}

+ 63 - 0
tests/unit/test_response_parsing.py

@@ -0,0 +1,63 @@
+import pytest
+from agenthub.micro.agent import parse_response, LLMOutputError
+from opendevin.events.action import (
+    AgentThinkAction,
+    FileWriteAction,
+)
+
+
+def test_parse_single_complete_json():
+    input_response = """
+    {
+        "action": "think",
+        "args": {
+            "thought": "The following typos were fixed:\\n* 'futur' -> 'future'\\n* 'imagin' -> 'imagine'\\n* 'techological' -> 'technological'\\n* 'responsability' -> 'responsibility'\\nThe corrected file is ./short_essay.txt."
+        }
+    }
+    """
+    expected = AgentThinkAction(thought="The following typos were fixed:\n* 'futur' -> 'future'\n* 'imagin' -> 'imagine'\n* 'techological' -> 'technological'\n* 'responsability' -> 'responsibility'\nThe corrected file is ./short_essay.txt.")
+    assert parse_response(input_response) == expected
+
+
+def test_parse_json_with_surrounding_text():
+    input_response = """
+    Some initial text that is not JSON formatted.
+    {
+        "action": "write",
+        "args": {
+            "path": "./updated_file.txt",
+            "content": "Updated text content here..."
+        }
+    }
+    Some trailing text that is also not JSON formatted.
+    """
+    expected = FileWriteAction(path="./updated_file.txt", content="Updated text content here...")
+    assert parse_response(input_response) == expected
+
+
+def test_parse_first_of_multiple_jsons():
+    input_response = """
+    I will firstly do
+    {
+        "action": "write",
+        "args": {
+            "path": "./short_essay.txt",
+            "content": "Text content here..."
+        }
+    }
+    Then I will continue with
+    {
+        "action": "think",
+        "args": {
+            "thought": "This should not be parsed."
+        }
+    }
+    """
+    expected = FileWriteAction(path="./short_essay.txt", content="Text content here...")
+    assert parse_response(input_response) == expected
+
+
+def test_invalid_json_raises_error():
+    input_response = '{"action": "write", "args": { "path": "./short_essay.txt", "content": "Missing closing brace"'
+    with pytest.raises(LLMOutputError):
+        parse_response(input_response)