Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of web search feature for better error resolution and new feature implementation #383

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
7 changes: 4 additions & 3 deletions src/agents/agent.py
Expand Up @@ -55,9 +55,9 @@ def __init__(self, base_model: str, search_engine: str, browser: Browser = None)
self.action = Action(base_model=base_model)
self.internal_monologue = InternalMonologue(base_model=base_model)
self.answer = Answer(base_model=base_model)
self.runner = Runner(base_model=base_model)
self.feature = Feature(base_model=base_model)
self.patcher = Patcher(base_model=base_model)
self.runner = Runner(base_model=base_model, search_engine=search_engine)
self.feature = Feature(base_model=base_model,search_engine=search_engine)
self.patcher = Patcher(base_model=base_model, search_engine=search_engine)
self.reporter = Reporter(base_model=base_model)
self.decision = Decision(base_model=base_model)

Expand Down Expand Up @@ -244,6 +244,7 @@ def subsequent_execute(self, prompt: str, project_name: str):
code_markdown=code_markdown,
commands=None,
error=prompt,
error_context=None,
system_os=os_system,
project_name=project_name
)
Expand Down
1 change: 1 addition & 0 deletions src/agents/error_analyzer/__init__.py
@@ -0,0 +1 @@
from .error_analyzer import ErrorAnalyzer
157 changes: 157 additions & 0 deletions src/agents/error_analyzer/error_analyzer.py
@@ -0,0 +1,157 @@
import json
import asyncio
from src.llm import LLM
from src.browser.search import DuckDuckGoSearch, BingSearch, GoogleSearch
from src.browser import Browser
from jinja2 import Environment, BaseLoader
from src.state import AgentState
from src.project import ProjectManager
import time
from src.agents.formatter import Formatter
from src.socket_instance import emit_agent

PROMPT = open("src/agents/error_analyzer/prompt.jinja2", "r").read().strip()

class ErrorAnalyzer:
def __init__(self, base_model: str, search_engine: str):
self.base_model = base_model
self.engine = search_engine
self.llm = LLM(model_id=base_model)
self.formatter = Formatter(base_model=base_model)

def render(
self,
conversation: str,
code_markdown: str,
commands: list,
system_os: str,
error: str
):
env = Environment(loader=BaseLoader())
template = env.from_string(PROMPT)
return template.render(
conversation=conversation,
code_markdown=code_markdown,
commands=commands,
system_os=system_os,
error=error
)

def validate(self, response: str):
response = response.strip().replace("```json", "```")

if response.startswith("```") and response.endswith("```"):
response = response[3:-3].strip()

try:
response = json.loads(response)
except Exception as _:
return False

if "error" not in response or "need_web" not in response:
return False
elif response["need_web"]!="True" and response["need_web"]!="False":
return False
else:
return response

async def open_page(self, project_name, pdf_download_url):
browser = await Browser().start()

await browser.go_to(pdf_download_url)
_, raw = await browser.screenshot(project_name)
data = await browser.extract_text()
await browser.close()

return browser, raw, data

def execute(
self,
conversation: list,
code_markdown: str,
commands: list,
error: str,
system_os: str,
project_name: str
):
prompt = self.render(
conversation=conversation,
code_markdown=code_markdown,
system_os=system_os,
commands=commands,
error=error
)
response = self.llm.inference(prompt, project_name)

valid_response = self.validate(response)
while not valid_response:
print("Invalid response from the model, trying again...")
return self.execute(
conversation,
code_markdown,
commands,
error,
system_os,
project_name
)

response = valid_response
command_error = error
error = response["error"]
need_web = response["need_web"]
main_cause = None

if "main_cause" in response:
main_cause = response["main_cause"]

total_error = error
if main_cause is not None:
total_error = error + " " + main_cause

results = {total_error: "Web searching wasn't required for this error."}

if need_web=="True":
ProjectManager().add_message_from_devika(project_name, "I need to search the web to fix the error...")
if commands is not None and len(commands) > 0:
new_state = AgentState().new_state()
new_state["internal_monologue"] = "Uh oh, an unseen error. Let's do web search to get it fixed."
new_state["terminal_session"]["title"] = "Terminal"
new_state["terminal_session"]["command"] = commands[-1]
new_state["terminal_session"]["output"] = command_error
AgentState().add_to_current_state(project_name, new_state)
time.sleep(1)

if main_cause is not None:
error = error + " " + main_cause

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

web_search = None

if self.engine == "duckduckgo":
web_search = DuckDuckGoSearch()
elif self.engine == "bing":
web_search = BingSearch()
elif self.engine == "google":
web_search = GoogleSearch()

web_search.search(error)
link = web_search.get_first_link()
browser, raw, error_context = loop.run_until_complete(self.open_page(project_name, link))
emit_agent("screenshot", {"data": raw, "project_name": project_name}, False)
results[error] = self.formatter.execute(error_context, project_name)
else:
if commands is not None and len(commands) > 0:
new_state = AgentState().new_state()
new_state["internal_monologue"] = "Oh seems like there is some error... :(. Trying to fix it"
new_state["terminal_session"]["title"] = "Terminal"
new_state["terminal_session"]["command"] = commands[-1]
new_state["terminal_session"]["output"] = error
AgentState().add_to_current_state(project_name, new_state)
time.sleep(1)

ProjectManager().add_message_from_devika(project_name, "I can resolve this error myself. Let me try...")
error = total_error

return results[error]
71 changes: 71 additions & 0 deletions src/agents/error_analyzer/prompt.jinja2
@@ -0,0 +1,71 @@
You are Devika, an AI Software Engineer. You have been talking to the user and this is the exchange so far:

```
{% for message in conversation %}
{{ message }}
{% endfor %}
```

Full Code:
~~~
{{ code_markdown }}
~~~

User's last message: {{ conversation[-1] }}

System Operating System: {{ system_os }}

You tried to execute the following commands to run this project:

{% if not commands %}
No commands were executed.
{% else %}
```
{% for command in commands %}
$ {{ command }}
{% endfor %}
```
{% endif %}


But it resulted in the following error:
```
{{ error }}
```

Now identify the error which is caused and give a one liner description and dont miss main lines of the error which mainly cause it. After that try to identify if you can rectify the error yourself or need web.

Response Format:
```
{
"error": "<Error Name as per error message>",
"main_cause": "<Main causes>",
"need_web": "<True/False>"
}
```
For example-

Example 1:
```
{
"error": "SyntaxError",
"main_cause": "missing colon after property id",
"need_web": "False"
}
```
Example 2:
```
{
"error": "Import Error",
"main_cause": "No module named 'numpy'",
"need_web": "True"
}
```

Rules:
- It should be your judgement that if you have enough knowledge to rectify the error yourself or you need to search the web. You should return true if you are 200% sure about correcting the error.
- You wrote the code, never address the user directly. You should not say things like "The code you provided", instead use "The code I wrote".
- Read the full context, including the code (if any) carefully to understand the erorr and steps required to fix the error while running the project.
- "error" should contain the error name and "main_cause" should contain the perfect one liner description of the error so that resolution is the easiest.
- "need_web" should be a string returning True or False.
Any response other than the JSON format will be rejected by the system. ONLY RESPOND WITH THE JSON OBJECT.