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

Git functionalities for the Devika #377

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/agents/action/prompt.jinja2
Expand Up @@ -17,6 +17,11 @@ The user could be asking the following:
- `feature` - Add a new feature to the project.
- `bug` - Fix a bug in the project.
- `report` - Generate a report on the project.
- `repo_init` - Initialize a new repository for the project.
- `repo_commit` - Commit the project to the repository.
- `reset_code` - Reset the code to the last commit.
- `start_auto_commit` - Enable auto-commit for the project. This will commit the changes to the repository automatically.
- `stop_auto_commit` - Stop auto-commit for the project. This will require the user to commit the changes manually.

Your response should be in the following format:
```
Expand Down
49 changes: 48 additions & 1 deletion src/agents/agent.py
Expand Up @@ -21,7 +21,7 @@
from src.browser import Browser
from src.browser import start_interaction
from src.filesystem import ReadCode
from src.services import Netlify
from src.services import Netlify, Git
from src.documenter.pdf import PDF

import json
Expand All @@ -35,6 +35,8 @@

class Agent:
def __init__(self, base_model: str, search_engine: str, browser: Browser = None):
print("New Agent Created\n")

if not base_model:
raise ValueError("base_model is required")

Expand All @@ -48,6 +50,7 @@ def __init__(self, base_model: str, search_engine: str, browser: Browser = None)
"""
Agents
"""
self.base_model = base_model
self.planner = Planner(base_model=base_model)
self.researcher = Researcher(base_model=base_model)
self.formatter = Formatter(base_model=base_model)
Expand All @@ -60,6 +63,7 @@ def __init__(self, base_model: str, search_engine: str, browser: Browser = None)
self.patcher = Patcher(base_model=base_model)
self.reporter = Reporter(base_model=base_model)
self.decision = Decision(base_model=base_model)
self.git = None

self.project_manager = ProjectManager()
self.agent_state = AgentState()
Expand Down Expand Up @@ -262,7 +266,42 @@ def subsequent_execute(self, prompt: str, project_name: str):

self.project_manager.add_message_from_devika(project_name, response)

elif action == "repo_init":
project_path = self.project_manager.get_project_path(project_name)
if self.git == None:
self.git = Git(project_path, self.base_model)

elif action == "repo_commit":
project_path = self.project_manager.get_project_path(project_name)
if self.git == None:
self.git = Git(project_path, self.base_model)

commit_message = self.git.generate_commit_message(project_name, conversation,code_markdown)
self.git.commit(commit_message)

elif action == "reset_code":
project_path = self.project_manager.get_project_path(project_name)
if self.git == None:
self.git = Git(project_path, self.base_model)

self.git.reset_to_previous_commit()

elif action == "start_auto_commit":
self.project_manager.start_auto_commit(project_name)

elif action == "stop_auto_commit":
self.project_manager.stop_auto_commit(project_name)

self.agent_state.set_agent_active(project_name, False)
# print("Auto Commit :: ", self.project_manager.get_auto_commit(project_name))
if self.project_manager.get_auto_commit(project_name):
print("\n Committing the code\n")
project_path = self.project_manager.get_project_path(project_name)
if self.git == None:
self.git = Git(project_path, self.base_model)

commit_message = self.git.generate_commit_message(project_name, conversation,code_markdown)
self.git.commit(commit_message)
self.agent_state.set_agent_completed(project_name, True)

def execute(self, prompt: str, project_name_from_user: str = None) -> str:
Expand Down Expand Up @@ -361,6 +400,14 @@ def execute(self, prompt: str, project_name_from_user: str = None) -> str:
self.coder.save_code_to_project(code, project_name)

self.agent_state.set_agent_active(project_name, False)
if self.project_manager.get_auto_commit(project_name):
project_path = self.project_manager.get_project_path(project_name)
if self.git == None:
self.git = Git(project_path, self.base_model)

commit_message = self.git.generate_commit_message(project_name, conversation,code_markdown)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reference of conversation and code_markdown in execute function

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can u add both these in execute function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure will add that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may i know ur discord username?

self.git.commit(commit_message)

self.agent_state.set_agent_completed(project_name, True)
self.project_manager.add_message_from_devika(project_name,
"I have completed the my task. \n"
Expand Down
22 changes: 22 additions & 0 deletions src/project.py
Expand Up @@ -11,6 +11,7 @@
class Projects(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
project: str
auto_commit: bool = False
message_stack_json: str


Expand Down Expand Up @@ -144,3 +145,24 @@ def project_to_zip(self, project: str):

def get_zip_path(self, project: str):
return f"{self.get_project_path(project)}.zip"

def start_auto_commit(self, project: str):
with Session(self.engine) as session:
project_state = session.query(Projects).filter(Projects.project == project).first()
if project_state:
project_state.auto_commit = True
session.commit()

def stop_auto_commit(self, project: str):
with Session(self.engine) as session:
project_state = session.query(Projects).filter(Projects.project == project).first()
if project_state:
project_state.auto_commit = False
session.commit()

def get_auto_commit(self, project: str):
with Session(self.engine) as session:
project_state = session.query(Projects).filter(Projects.project == project).first()
if project_state:
return project_state.auto_commit
return False
73 changes: 71 additions & 2 deletions src/services/git.py
@@ -1,8 +1,77 @@
import json
import git as GitPython

from jinja2 import Environment, BaseLoader
from src.llm import LLM
PROMPT = open("src/services/prompt.jinja2").read().strip()

class Git:
def __init__(self, path):
self.repo = GitPython.Repo(path)

def __init__(self, path, base_model: str):
self.llm = LLM(model_id=base_model)
try:
self.repo = GitPython.Repo(path)
except GitPython.exc.InvalidGitRepositoryError:
self.repo = self.initialize(path)

def render(
self, conversation: str, code_markdown: str, code_diff: str
) -> str:
env = Environment(loader=BaseLoader())
template = env.from_string(PROMPT)
return template.render(
conversation=conversation,
code_markdown=code_markdown,
code_diff=code_diff
)

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

try:
response = response.split("```")[1].split("```")[0]
response = json.loads(response)
except Exception as _:
return False

if "commit_message" not in response:
return False
else:
return response["commit_message"]

def initialize(self, path):
return GitPython.Repo.init(path)

def commit(self, message):
self.repo.index.add("*")
self.repo.index.commit(message)

def generate_commit_message(self, project_name, conversation, code_markdown):
# Get the code diff
try:
code_diff = self.repo.git.diff()
except GitPython.exc.GitCommandError:
code_diff = ""

prompt = self.render(conversation, code_markdown, code_diff)
response = self.llm.inference(prompt, project_name)
print(response)

valid_response = self.validate_response(response)

while not valid_response:
print("Invalid response from the model, trying again...")
return self.generate_commit_message(project_name, conversation, code_markdown)

return valid_response

def reset_to_previous_commit(self):
try:
self.repo.git.reset("--hard", "HEAD^")
return True
except GitPython.exc.GitCommandError as e:
print(f"Error resetting to previous commit: {e}")
return False

def clone(self, url, path):
return GitPython.Repo.clone_from(url, path)
Expand Down
52 changes: 52 additions & 0 deletions src/services/prompt.jinja2
@@ -0,0 +1,52 @@
You are Devika, an AI Software Engineer. You have been talking to the user and this is your exchanges so far:

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

Code Markdown: {{code_markdown}}

Difference from the last commit: {{code_diff}}

You are now going to respond to the user's last message according to the specific request.

Your response should be in the following format:
```
{
"commit_message": "Initial commit"
}
```

More examples:
```
{
"commit_message": "Add xxx functionality to the project"
}
```

```
{
"commit_message": "Implement user authentication feature"
}
```

```
{
"commit_message": "Fix bug causing application crash on startup"
}
```

```
{
"commit_message": "Update README with installation instructions"
}
```

```
{
"commit_message": "Refactor code for better readability and performance"
}
```


You should generate a commit message that is appropriate for the user's request. If there is commit message already in user's last message, you should use that as the commit message. If there is no commit message, you should generate a commit message based on the code markdown and the differences from last commit

Any response other than the JSON format will be rejected by the system.