diff --git a/README.md b/README.md index c35e010..35d2266 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ ```ascii - ██▀███ ▄▄▄ ▄▄▄ ██▓▓█████▄ + ██▀███ ▄▄▄ ▄▄▄ ██▓▓█████▄ ▓██ ▒ ██▒▒████▄ ▒████▄ ▓██▒▒██▀ ██▌ ▓██ ░▄█ ▒▒██ ▀█▄ ▒██ ▀█▄ ▒██▒░██ █▌ ▒██▀▀█▄ ░██▄▄▄▄██ ░██▄▄▄▄██ ░██░░▓█▄ ▌ - ░██▓ ▒██▒ ▓█ ▓██▒ ██▓ ▓█ ▓██▒░██░░▒████▓ - ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░ ▒▓▒ ▒▒ ▓▒█░░▓ ▒▒▓ ▒ - ░▒ ░ ▒░ ▒ ▒▒ ░ ░▒ ▒ ▒▒ ░ ▒ ░ ░ ▒ ▒ - ░░ ░ ░ ▒ ░ ░ ▒ ▒ ░ ░ ░ ░ - ░ ░ ░ ░ ░ ░ ░ ░ - ░ ░ + ░██▓ ▒██▒ ▓█ ▓██▒ ██▓ ▓█ ▓██▒░██░░▒████▓ + ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░ ▒▓▒ ▒▒ ▓▒█░░▓ ▒▒▓ ▒ + ░▒ ░ ▒░ ▒ ▒▒ ░ ░▒ ▒ ▒▒ ░ ▒ ░ ░ ▒ ▒ + ░░ ░ ░ ▒ ░ ░ ▒ ▒ ░ ░ ░ ░ + ░ ░ ░ ░ ░ ░ ░ ░ + ░ ░ ``` [![Python Versions](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org) @@ -76,7 +76,7 @@ What sets RA.Aid apart is its ability to handle complex programming tasks that e 1. **Research** 🔍 - Gather and analyze information 2. **Planning** 📋 - Develop execution strategy 3. **Implementation** ⚡ - Execute the plan with AI assistance - + Each stage is powered by dedicated AI agents and specialized toolsets. - **Advanced AI Integration**: Built on LangChain and leverages the latest LLMs for natural language understanding and generation. - **Human-in-the-Loop Interaction**: Optional mode that enables the agent to ask you questions during task execution, ensuring higher accuracy and better handling of complex tasks that may require your input or clarification @@ -260,7 +260,7 @@ RA.Aid supports multiple providers through environment variables: Expert Tool Environment Variables: - `EXPERT_OPENAI_API_KEY`: API key for expert tool using OpenAI provider - `EXPERT_ANTHROPIC_API_KEY`: API key for expert tool using Anthropic provider -- `EXPERT_OPENROUTER_API_KEY`: API key for expert tool using OpenRouter provider +- `EXPERT_OPENROUTER_API_KEY`: API key for expert tool using OpenRouter provider - `EXPERT_OPENAI_API_BASE`: Base URL for expert tool using OpenAI-compatible provider You can set these permanently in your shell's configuration file (e.g., `~/.bashrc` or `~/.zshrc`): @@ -279,9 +279,7 @@ export OPENROUTER_API_KEY=your_api_key_here export OPENAI_API_BASE=your_api_base_url ``` -Note: The expert tool defaults to OpenAI's o1-preview model with the OpenAI provider, but this can be configured using the --expert-provider flag along with the corresponding EXPERT_*_KEY environment variables. - -#### Custom Model Examples +### Custom Model Examples 1. **Using Anthropic (Default)** ```bash @@ -320,6 +318,17 @@ Note: The expert tool defaults to OpenAI's o1-preview model with the OpenAI prov ra-aid -m "Your task" --expert-provider openai --expert-model o1-preview ``` +Aider specific Environment Variables you can add: + +- `AIDER_FLAGS`: Optional comma-separated list of flags to pass to the underlying aider tool (e.g., "yes-always,dark-mode") + +```bash +# Optional: Configure aider behavior +export AIDER_FLAGS="yes-always,dark-mode,no-auto-commits" +``` + +Note: For `AIDER_FLAGS`, you can specify flags with or without the leading `--`. Multiple flags should be comma-separated, and spaces around flags are automatically handled. For example, both `"yes-always,dark-mode"` and `"--yes-always, --dark-mode"` are valid. + **Important Notes:** - Performance varies between models. The default Claude 3 Sonnet model currently provides the best and most reliable results. - Model configuration is done via command line arguments: `--provider` and `--model` @@ -329,7 +338,7 @@ Note: The expert tool defaults to OpenAI's o1-preview model with the OpenAI prov RA.Aid implements a three-stage architecture for handling development and research tasks: -1. **Research Stage**: +1. **Research Stage**: - Gathers information and context - Analyzes requirements - Identifies key components and dependencies diff --git a/ra_aid/tools/programmer.py b/ra_aid/tools/programmer.py index f29c8b8..1656a6d 100644 --- a/ra_aid/tools/programmer.py +++ b/ra_aid/tools/programmer.py @@ -44,54 +44,91 @@ Returns: { "output": stdout+stderr, "return_code": 0 if success, "success": True "--no-auto-commits", "--dark-mode", "--no-suggest-shell-commands", - "-m" ] - + + # if environment variable AIDER_FLAGS exists then parse + if 'AIDER_FLAGS' in os.environ: + # wrap in try catch in case of any error and log the error + try: + command.extend(parse_aider_flags(os.environ['AIDER_FLAGS'])) + except Exception as e: + print(f"Error parsing AIDER_FLAGS: {e}") + + # ensure message aider argument is always present + command.append("-m") + command.append(input.instructions) - + if input.files: command.extend(input.files) - + # Create a pretty display of what we're doing task_display = [ "## Instructions\n", f"{input.instructions}\n" ] - + if input.files: task_display.extend([ "\n## Files\n", *[f"- `{file}`\n" for file in input.files] ]) - + markdown_content = "".join(task_display) console.print(Panel(Markdown(markdown_content), title="🤖 Aider Task", border_style="bright_blue")) - + try: # Run the command interactively print() output, return_code = run_interactive_command(command) print() - + # Return structured output return { "output": truncate_output(output.decode() if output else ""), "return_code": return_code, "success": return_code == 0 } - + except Exception as e: print() error_text = Text() error_text.append("Error running programming task:\n", style="bold red") error_text.append(str(e), style="red") console.print(error_text) - + return { "output": str(e), "return_code": 1, "success": False } +def parse_aider_flags(aider_flags: str) -> List[str]: + """Parse a string of aider flags into a list of flags. + + Args: + aider_flags: A string containing comma-separated flags, with or without leading dashes. + Can contain spaces around flags and commas. + + Returns: + A list of flags with proper '--' prefix. + + Examples: + >>> parse_aider_flags("yes-always,dark-mode") + ['--yes-always', '--dark-mode'] + >>> parse_aider_flags("--yes-always, --dark-mode") + ['--yes-always', '--dark-mode'] + >>> parse_aider_flags("") + [] + """ + if not aider_flags.strip(): + return [] + + # Split by comma and strip whitespace + flags = [flag.strip() for flag in aider_flags.split(",")] + + # Add '--' prefix if not present and filter out empty flags + return [f"--{flag.lstrip('-')}" for flag in flags if flag.strip()] + # Export the functions __all__ = ['run_programming_task'] diff --git a/tests/test_programmer.py b/tests/test_programmer.py new file mode 100644 index 0000000..fc99963 --- /dev/null +++ b/tests/test_programmer.py @@ -0,0 +1,53 @@ +import pytest +from ra_aid.tools.programmer import parse_aider_flags + +# Test cases for parse_aider_flags function +test_cases = [ + # Test case format: (input_string, expected_output, test_description) + ( + "yes-always,dark-mode", + ["--yes-always", "--dark-mode"], + "basic comma separated flags without dashes" + ), + ( + "--yes-always,--dark-mode", + ["--yes-always", "--dark-mode"], + "comma separated flags with dashes" + ), + ( + "yes-always, dark-mode", + ["--yes-always", "--dark-mode"], + "comma separated flags with space" + ), + ( + "--yes-always, --dark-mode", + ["--yes-always", "--dark-mode"], + "comma separated flags with dashes and space" + ), + ( + "", + [], + "empty string" + ), + ( + " yes-always , dark-mode ", + ["--yes-always", "--dark-mode"], + "flags with extra whitespace" + ), + ( + "--yes-always", + ["--yes-always"], + "single flag with dashes" + ), + ( + "yes-always", + ["--yes-always"], + "single flag without dashes" + ) +] + +@pytest.mark.parametrize("input_flags,expected,description", test_cases) +def test_parse_aider_flags(input_flags, expected, description): + """Table-driven test for parse_aider_flags function.""" + result = parse_aider_flags(input_flags) + assert result == expected, f"Failed test case: {description}"