diff --git a/.gitignore b/.gitignore
index 5a34e1c9..0521c154 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# Application specific
+.devon.config
+
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@@ -191,3 +194,6 @@ dist
.yarn
build
+
+package-lock.json
+poetry.lock
\ No newline at end of file
diff --git a/README.md b/README.md
index e570b41c..057b8a2d 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ Configuring Devon CLI...
? Select the model name:
claude-opus
gpt4-o
+ gemini-pro
llama-3-70b
❯ ollama/deepseek-coder:6.7b
```
diff --git a/devon-tui/new b/devon-tui/new
deleted file mode 100644
index 9a875884..00000000
--- a/devon-tui/new
+++ /dev/null
@@ -1 +0,0 @@
-ello, this is a new file.
diff --git a/devon-tui/source/cli.tsx b/devon-tui/source/cli.tsx
index 19775dfb..7708ea8b 100644
--- a/devon-tui/source/cli.tsx
+++ b/devon-tui/source/cli.tsx
@@ -46,6 +46,7 @@ const cli = meow(
$ devon start --api_key=YOUR_API_KEY
$ devon start --port 8080 --api_key=YOUR_API_KEY
$ devon start --model=gpt4-o --api_key=YOUR_API_KEY
+ $ devon start --model=gemini-pro --api_key=YOUR_API_KEY
$ devon start --model=claude-opus --api_key=YOUR_API_KEY
$ devon start --model=llama-3-70b --api_key=YOUR_API_KEY
$ devon start --model=custom --api_base=https://api.example.com --prompt_type=anthropic --api_key=YOUR_API_KEY
@@ -67,7 +68,8 @@ const cli = meow(
type: 'string',
},
debug: {
- type: 'boolean'
+ type: 'boolean',
+ default: false
},
},
},
@@ -85,14 +87,14 @@ const { input } = cli;
if (input[0] === 'configure') {
// Handle the configure subcommand
console.log('Configuring Devon CLI...');
-
+
inquirer
.prompt([
{
type: 'list',
name: 'modelName',
message: 'Select the model name:',
- choices: ['claude-opus', 'gpt4-o', 'llama-3-70b', 'ollama/deepseek-coder:6.7b', 'custom'],
+ choices: ['claude-opus', 'gpt4-o', 'gemini-pro', 'llama-3-70b', 'ollama/deepseek-coder:6.7b', 'custom'],
},
])
.then((answers) => {
@@ -169,19 +171,31 @@ if (input[0] === 'configure') {
let api_base: string | undefined = undefined
let prompt_type: string | undefined = undefined
- if (cli.flags.apiKey){
- api_key = cli.flags['apiKey'];
- } else if (process.env['OPENAI_API_KEY']){
+ if (process.env['OPENAI_API_KEY']){
api_key = process.env['OPENAI_API_KEY'];
modelName = "gpt4-o"
+ } else if (process.env['GEMINI_API_KEY']){
+ api_key = process.env['GEMINI_API_KEY'];
+ modelName = "gemini-pro"
} else if (process.env['ANTHROPIC_API_KEY']){
api_key = process.env['ANTHROPIC_API_KEY'];
modelName = "claude-opus"
} else if (process.env['GROQ_API_KEY']){
api_key = process.env['GROQ_API_KEY'];
modelName = "llama-3-70b"
+ } else if (cli.flags['apiKey']){
+ api_key = cli.flags['apiKey'];
+
+ if(api_key != "FOSS") {
+ if(cli.flags['model']) {
+ modelName = cli.flags['model'] as string;
+ } else {
+ console.log('Please provide a model name. Allowed values are gpt4-o, gemini-pro, claude-opus, llama-3-70b or ollama.');
+ process.exit(1);
+ }
+ }
} else {
- console.log('Please provide an API key using the --api_key option or by setting OPENAI_API_KEY or ANTHROPIC_API_KEY.');
+ console.log('Please provide an API key using the --api_key option or by setting OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GROQ_API_KEY.');
process.exit(1);
}
@@ -218,7 +232,7 @@ if (input[0] === 'configure') {
);
process.exit(1);
}
- console.log( ['server', '--port', port.toString(), '--model', modelName as string, '--api_key', api_key as string, '--api_base', api_base as string, '--prompt_type', prompt_type as string])
+ console.log( ['server', '--port', port.toString(), '--model', modelName as string, '--api_key', api_key as string])
let reset = false
@@ -236,7 +250,7 @@ if (input[0] === 'configure') {
const subProcess = childProcess.spawn(
'devon_agent',
- ['server', '--port', port.toString(), '--model', modelName as string, '--api_key', api_key as string, '--api_base', api_base as string, '--prompt_type', prompt_type as string],
+ ['server', '--port', port.toString(), '--model', modelName as string, '--api_key', api_key as string],
{
signal: controller.signal
},
diff --git a/devon_agent/__main__.py b/devon_agent/__main__.py
index acf39565..307f7522 100644
--- a/devon_agent/__main__.py
+++ b/devon_agent/__main__.py
@@ -35,9 +35,11 @@ def server(port, model, api_key, prompt_type, api_base):
app.prompt_type = prompt_type
app.model = model
- with open(os.path.join(os.getcwd(), ".devon.config"), "r") as f:
- config = f.read()
- app.config = json.loads(config)
+ config_path = os.path.join(os.getcwd(), ".devon.config")
+ if os.path.exists(config_path):
+ with open(config_path, "r") as f:
+ config = f.read()
+ app.config = json.loads(config)
uvicorn.run(app, host="0.0.0.0", port=port)
@@ -63,9 +65,11 @@ def headless(model, api_key, prompt_type, api_base, headless):
app.model = model
app.headless = headless
- with open(os.path.join(os.getcwd(), ".devon.config"), "r") as f:
- config = f.read()
- app.config = json.loads(config)
+ config_path = os.path.join(os.getcwd(), ".devon.config")
+ if os.path.exists(config_path):
+ with open(config_path, "r") as f:
+ config = f.read()
+ app.config = json.loads(config)
agent = TaskAgent(
name="Devon",
diff --git a/devon_agent/agents/default/agent.py b/devon_agent/agents/default/agent.py
index c2c04419..98d095fd 100644
--- a/devon_agent/agents/default/agent.py
+++ b/devon_agent/agents/default/agent.py
@@ -5,15 +5,14 @@
import traceback
from typing import Optional, Tuple
-from devon_agent.agents.model import AnthropicModel, GroqModel, ModelArguments, OllamaModel, OpenAiModel
+from devon_agent.agents.model import AnthropicModel, GroqModel, ModelArguments, OllamaModel, OpenAiModel, GeminiModel
from devon_agent.agents.default.anthropic_prompts import anthropic_history_to_bash_history, anthropic_last_user_prompt_template_v3, anthropic_system_prompt_template_v3, anthropic_commands_to_command_docs
from devon_agent.agents.default.openai_prompts import openai_last_user_prompt_template_v3, openai_system_prompt_template_v3, openai_commands_to_command_docs
-from devon_agent.agents.default.anthropic_prompts import (
- parse_response
-)
from devon_agent.agents.default.llama3_prompts import llama3_commands_to_command_docs, llama3_history_to_bash_history, llama3_last_user_prompt_template_v1, llama3_parse_response, llama3_system_prompt_template_v1
+from devon_agent.agents.default.gemini_prompts import gemini_commands_to_command_docs, gemini_history_to_bash_history, gemini_last_user_prompt_template_v1, gemini_parse_response, gemini_system_prompt_template_v1
from devon_agent.agents.default.codegemma_prompts import llama3_7b_commands_to_command_docs, llama3_7b_history_to_bash_history, llama3_7b_last_user_prompt_template_v1, llama3_7b_parse_response, llama3_7b_system_prompt_template_v1
+
from devon_agent.tools.utils import get_cwd
from devon_agent.udiff import Hallucination
@@ -47,6 +46,7 @@ def run(self, session: "Session", observation: str = None): ...
class TaskAgent(Agent):
default_models = {
"gpt4-o": OpenAiModel,
+ "gemini-pro": GeminiModel,
"claude-opus": AnthropicModel,
"llama-3-70b": GroqModel,
"ollama/deepseek-coder:6.7b": OllamaModel
@@ -56,6 +56,9 @@ class TaskAgent(Agent):
"gpt4-o": {
"prompt_type": "openai",
},
+ "gemini-pro": {
+ "prompt_type": "gemini",
+ },
"claude-opus": {
"prompt_type": "anthropic",
},
@@ -204,6 +207,32 @@ def _prepare_llama3(self, task, editor, session):
messages = [{"role": "user", "content": last_user_prompt}]
return messages, system_prompt
+
+ def _prepare_gemini(self, task, editor, session):
+ time.sleep(3)
+
+ command_docs = (
+ "Custom Commands Documentation:\n"
+ + gemini_commands_to_command_docs(
+ list(session.generate_command_docs().values())
+ )
+ + "\n"
+ )
+
+ history = gemini_history_to_bash_history(self.chat_history)
+ system_prompt = gemini_system_prompt_template_v1(command_docs)
+ last_user_prompt = gemini_last_user_prompt_template_v1(
+ task, history, editor, get_cwd(
+ {
+ "session": session,
+ "environment": session.default_environment,
+ "state": session.state
+ }
+ ), session.base_path, self.scratchpad
+ )
+
+ messages = [{"role": "user", "content": last_user_prompt}]
+ return messages, system_prompt
def _prepare_ollama(self, task, editor, session):
time.sleep(3)
@@ -262,7 +291,8 @@ def predict(
"anthropic": self._prepare_anthropic,
"openai": self._prepare_openai,
"llama3": self._prepare_llama3,
- "ollama": self._prepare_ollama
+ "ollama": self._prepare_ollama,
+ "gemini": self._prepare_gemini,
}
if not self.prompt_type:
diff --git a/devon_agent/agents/default/gemini_prompts.py b/devon_agent/agents/default/gemini_prompts.py
new file mode 100644
index 00000000..c1200448
--- /dev/null
+++ b/devon_agent/agents/default/gemini_prompts.py
@@ -0,0 +1,159 @@
+from typing import Dict, List, Union
+
+def gemini_commands_to_command_docs(commands: List[Dict]):
+ doc = ""
+ for command in commands:
+ doc += f"{command['signature']}\n{command['docstring']}\n"
+ return doc
+
+def editor_repr(editor):
+ return "\n\n".join(f"{file}:\n{editor[file]}" for file in editor)
+
+def gemini_history_to_bash_history(history):
+ # self.history.append(
+ # {
+ # "role": "assistant",
+ # "content": output,
+ # "thought": thought,
+ # "action": action,
+ # "agent": self.name,
+
+ bash_history = ""
+ for entry in history:
+ if entry["role"] == "user":
+ result = entry["content"].strip() if entry["content"] else "" + "\n"
+ bash_history += f"\n{result}\n"
+ elif entry["role"] == "assistant":
+ bash_history += f"""
+
+{entry['thought']}
+
+{entry['action'][1:]}
+
+
+"""
+ return bash_history
+
+def object_to_xml(data: Union[dict, bool], root="object"):
+ xml = f"<{root}>"
+ if isinstance(data, dict):
+ xml += "".join(object_to_xml(value, key) for key, value in data.items())
+ elif isinstance(data, (list, tuple, set)):
+ xml += "".join(object_to_xml(item, "item") for item in data)
+ else:
+ xml += str(data)
+ xml += f"{root}>"
+ return xml
+
+def print_tree(directory, level=0, indent=""):
+ return "".join(f"\n{indent}├── {name}/" + print_tree(content, level + 1, indent + "│ ") if isinstance(content, dict) else f"\n{indent}├── {name}" for name, content in directory.items())
+
+def gemini_system_prompt_template_v1(command_docs: str):
+ return f"""
+
+ You are an autonomous programmer, and you're working directly in the command line with a special interface.
+
+ Environment:
+- Editor (): Open, edit, and auto-save code files. Focus on relevant files for each bug fix.
+- Terminal: Execute commands to perform actions. Modify failed commands before retrying.
+- History (): Log of previous thoughts and actions. Act as if you've had these thoughts and performed these actions.
+
+Constraints:
+- Maintain proper formatting and adhere to the project's coding conventions.
+- Keep only relevant files open. Close inactive files.
+- Modify failed commands before retrying.
+- Use efficient search techniques to locate relevant code elements.
+- Verify fixes resolve the original issue before submitting.
+- Prioritize general fixes over specific ones.
+- Ask for user input when needed for feedback, clarification, or guidance.
+
+
+
+{command_docs}
+
+
+Shell prompt format: $
+Required fields for each response:
+
+Your reflection, planning, and justification
+
+
+Information you want to write down
+
+
+A single executable command (no interactive commands)
+
+
+"""
+
+def gemini_last_user_prompt_template_v1(issue, history, editor, cwd, root_dir, scratchpad):
+ return f"""
+
+Objective: {issue}
+
+Instructions:
+- Edit files and run checks/tests
+- Submit with 'submit' when done
+- No interactive commands, write scripts instead
+
+
+- One command at a time
+- Wait for feedback after each command
+- Locate classes/functions over files
+- Use 'no_op' for thinking time
+- Issue title/first line describes it succinctly
+
+
+- Write unit tests to verify fixes
+- Run tests frequently to catch regressions
+- Test edge cases and error handling
+- Manually verify UI and integration tests
+- Ensure tests pass before submitting
+
+
+- Identify root cause and failure case
+- Fix underlying logic bug generally
+- Trace error to source
+- Identify flawed logic or edge case handling
+- Devise robust solution for core problem
+- Test fix thoroughly for potential impacts
+
+
+- Use 'no_op' to pause and think
+- Match source lines precisely
+- Scroll to lines before changing
+- Make one change at a time
+- Finish edits before testing
+- Access limited to {root_dir}
+- Current directory: {cwd}
+
+
+{history}
+
+
+{editor}
+
+
+{scratchpad}
+
+
+{root_dir}
+
+{cwd} $
+"""
+
+def gemini_parse_response(response):
+ if "" in response:
+ thought = response.split("")[1].split("")[0]
+ action = response.split("")[1].split("")[0]
+ scratchpad = None
+ if "" in response:
+ scratchpad = response.split("")[1].split("")[0]
+ else:
+ thought = response.split("")[1].split("")[0]
+ action = response.split("")[1].split("")[0]
+ scratchpad = None
+ if "" in response:
+ scratchpad = response.split("")[1].split("")[0]
+
+ return thought, action, scratchpad
diff --git a/devon_agent/agents/model.py b/devon_agent/agents/model.py
index 8f73c7aa..feefdf5d 100644
--- a/devon_agent/agents/model.py
+++ b/devon_agent/agents/model.py
@@ -158,6 +158,59 @@ def query(self, messages: list[dict[str, str]], system_message: str = "") -> str
response = model_completion.choices[0].message.content.rstrip("")
return response + ""
+class GeminiModel:
+ MODELS = {
+ "gemini/gemini-1.5-pro": {
+ "max_tokens": 4096,
+ }
+ }
+
+ SHORTCUTS = {
+ "gemini-pro": "gemini/gemini-1.5-pro"
+ }
+ def __init__(self, args: ModelArguments):
+ self.args = args
+ self.api_model = self.SHORTCUTS.get(args.model_name, args.model_name)
+ self.model_metadata = self.MODELS[self.api_model]
+ self.prompt_type = 'gemini'
+ if args.api_key is not None:
+ self.api_key = args.api_key
+ else:
+ self.api_key = os.getenv("GEMINI_API_KEY")
+
+ def query(self, messages: list[dict[str, str]], system_message: str = "") -> str:
+
+ print(self.api_model);
+
+ model_completion = completion(
+ messages=[{"role": "system", "content": system_message}] + messages,
+ max_tokens=self.model_metadata["max_tokens"],
+ model=self.api_model,
+ temperature=self.args.temperature,
+ stop=[""],
+ safety_settings=[
+ {
+ "category": "HARM_CATEGORY_HARASSMENT",
+ "threshold": "BLOCK_NONE",
+ },
+ {
+ "category": "HARM_CATEGORY_HATE_SPEECH",
+ "threshold": "BLOCK_NONE",
+ },
+ {
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
+ "threshold": "BLOCK_NONE",
+ },
+ {
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
+ "threshold": "BLOCK_NONE",
+ },
+ ]
+ )
+
+ response = model_completion.choices[0].message.content.rstrip("")
+ return response + ""
+
class OllamaModel:
def __init__(self, args: ModelArguments):
diff --git a/devon_agent/server.py b/devon_agent/server.py
index 216b35c1..2b6b9bcb 100644
--- a/devon_agent/server.py
+++ b/devon_agent/server.py
@@ -275,6 +275,9 @@ async def event_generator():
if os.environ.get("OPENAI_API_KEY"):
app.api_key = os.environ.get("OPENAI_API_KEY")
app.model = "gpt4-o"
+ if os.environ.get("GEMINI_API_KEY"):
+ app.api_key = os.environ.get("GEMINI_API_KEY")
+ app.model = "gemini-pro"
elif os.environ.get("ANTHROPIC_API_KEY"):
app.api_key = os.environ.get("ANTHROPIC_API_KEY")
app.model = "claude-opus"
diff --git a/devon_agent/session.py b/devon_agent/session.py
index 5623f724..1858e9da 100644
--- a/devon_agent/session.py
+++ b/devon_agent/session.py
@@ -110,7 +110,7 @@ def __init__(self, args: SessionArguments, agent):
self.name = args.name
self.agent_branch = "devon_agent_" + self.name
self.global_config = args.config
- self.excludes = self.global_config["excludes"] if self.global_config else []
+ self.excludes = self.global_config["excludes"] if "excludes" in self.global_config else []
local_environment = LocalEnvironment(args.path)
local_environment.register_tools({
diff --git a/devon_agent/tools/codeindex.py b/devon_agent/tools/codeindex.py
index c9ada720..a8e189d6 100644
--- a/devon_agent/tools/codeindex.py
+++ b/devon_agent/tools/codeindex.py
@@ -36,7 +36,7 @@ class FindFunctionTool(Tool):
@property
def name(self):
- return "create_file"
+ return "find_function"
def setup(self, ctx, **kwargs):
diff --git a/devon_agent/tools/editortools.py b/devon_agent/tools/editortools.py
index d8f0fb53..219fe59f 100644
--- a/devon_agent/tools/editortools.py
+++ b/devon_agent/tools/editortools.py
@@ -405,7 +405,7 @@ def supported_formats(self):
@property
def name(self):
- return "scroll_up_in_editor"
+ return "scroll_up"
def setup(self, ctx: ToolContext):
ctx["state"].editor.files = {}
diff --git a/pyproject.toml b/pyproject.toml
index 723b74fb..4423cea0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,6 +37,7 @@ dspy-ai = {version = "^2.4.9", optional = true}
aiosqlite = "^0.20.0"
greenlet = "^3.0.3"
+google-generativeai = "^0.5.4"
[tool.poetry.extras]
swebench = ["swebench", "datasets", "gymnasium"]
experimental = ["dspy-ai"]