2024.01.11

Aider: Your Own Personal Junior Developer

こんにちは、次世代システム研究室のN.M.です。

 

Introduction

 

Aider is an AI-driven tool for pair programming with an AI-powered Junior Developer, directly in your terminal. It integrates with OpenAI’s models, including GPT-3, GPT-4, and GPT-4-turbo, to enhance the coding experience. The primary language used for Aider is Python, complemented by Scheme. This open-source project is licensed under the Apache-2.0 license and has attracted significant attention with thousands of stars and 750 forks on GitHub. The project’s main page can be found at paul-gauthier/aider on GitHub.

Aider significantly reduces the cognitive load for developers by automating routine coding tasks. This tool eliminates the need for manual tasks such as copying, pasting, and executing Git commits, thereby streamlining the development process. The use of git allows unwanted changes to be easily undone with the /undo command.

Well-crafted prompts enhance AI performance, allowing developers to focus more on problem-solving and creative aspects of coding, rather than getting bogged down by repetitive tasks. This reduction in cognitive load not only improves productivity but also potentially enhances code quality by minimizing human error.

 

Local Models?

 

Aider can be configured with local LLMs but it is recommended to use the OpenAI GPT-4 and above for a smoother experience.

 

Install

 

Installation couldn’t be simpler:
$ pip install aider-chat
$ export OPENAI_API_KEY=your-key-goes-here
$ aider hello.js

Using git repo: .git
Added hello.js to the chat.

hello.js> write a js script that prints hello world
Aider is written in Python and uses pip to install.

To use aider with the GPT-4 Turbo and the 128k context window, run the command below in your project root:
aider --4-turbo
Easy isn’t it?

Rust linked-list example

Let’s have a look at using Aider to help us write some Rust.

Imagine we have a simple Rust project with this structure:
.
├── Cargo.toml
└── src
└── main.rs
 

… and the contents of main.rs is:
#[derive(Debug)]
#[derive(PartialEq)]
pub struct LinkedList<T>(Option<(T, Box<LinkedList<T>>)>);

impl<T:PartialOrd> LinkedList<T>{
    pub fn new() -> Self{
        LinkedList(None)
    }

    pub fn push_front(&mut self, data: T){
        let t = self.0.take();
        self.0 = Some((data, Box::new(LinkedList(t))));
    }

    pub fn push_back(&mut self, data:T) {
        match self.0 {
            Some((_, ref mut child)) => child.push_back(data),
            None => self.push_front(data),
        }
    }
}

fn main() {
    let mut ll = LinkedList::new();
    ll.push_front(1);
    ll.push_back(2);
    ll.push_back(4);

    println!("ll = {:?}", ll);
}
We have the bare bones for a linked-list implementation in Rust. I would like to add a method that provides ordered insert functionality, let’s ask Aider to do it for us.

First, we need to start Aider in our project root directory.
➜ aider --4-turbo
Aider v0.21.0
Newer version v0.21.1 is available. To upgrade, run:
/Users/nm/.asdf/installs/python/3.11.4/bin/python3.11 -m pip install --upgrade aider-chat
Add .aider* to .gitignore (recommended)? y
 

Aider is prompting us if we want to add a wild card for Aider files to .gitignore. Let’s accept the default suggestion and hit return:
Added .aider* to .gitignore
Model: gpt-4-1106-preview using udiff edit format
Git repo: .git with 0 files
Repo-map: using 1024 tokens
Use /help to see in-chat commands, run with --help to see cmd line args
───────────────────────────────────────────────────────────────────────
>
 

We also saw that a new version of Aider is available. This happens often because the project is under rapid development. Let’s upgrade Aider, exit Aider (ctl-d), and use the suggested commands:
/Users/nm/.asdf/installs/python/3.11.4/bin/python3.11 -m pip install --upgrade aider-chat
...
Requirement already satisfied: wcwidth==0.2.12 in /Users/nm/.asdf/installs/python/3.11.4/lib/python3.11/site-packages (from aider-chat) (0.2.12)
Installing collected packages: aider-chat
Attempting uninstall: aider-chat
Found existing installation: aider-chat 0.21.0
Uninstalling aider-chat-0.21.0:
Successfully uninstalled aider-chat-0.21.0
Successfully installed aider-chat-0.21.1
 

Now we can get back to the Aider prompt:
➜ aider --4-turbo
Aider v0.21.1
Model: gpt-4-1106-preview using udiff edit format
Git repo: .git with 0 files
Repo-map: using 1024 tokens
Use /help to see in-chat commands, run with --help to see cmd line args
───────────────────────────────────────────────────────────────────────
>
This time we see we have the latest version, we are using the latest GPT-4 Turbo, our git repo has 0 files added, and our Repo-map uses 1024 tokens. I will explain more about Repo-maps later, but for now, just know that they are a way to restrict token use for larger projects.

OK, now we are ready to get back to our initial task of asking Aider to create an ordered insert method. First, we need to add the file containing our code to Aider. Aider can only read and change files that have been added.
> /add src/main.rs

Added /Users/nm/projects/blogs/aider/linked-list/src/main.rs to the chat
───────────────────────────────────────────────────────────────────────
src/main.rs
>
 

Finally, let’s ask Aider to make the changes we want:
> Add a method ordered_insert
--- src/main.rs
 +++ src/main.rs
 @@ ... @@
  impl<T:PartialOrd> LinkedList<T>{
      pub fn new() -> Self{
          LinkedList(None)
      }

      pub fn push_front(&mut self, data: T){
          let t = self.0.take();
          self.0 = Some((data, Box::new(LinkedList(t))));
      }

      pub fn push_back(&mut self, data:T) {
          match self.0 {
              Some((_, ref mut child)) => child.push_back(data),
              None => self.push_front(data),
          }
      }
 +
 +    pub fn ordered_insert(&mut self, data: T) {
 +        match self.0 {
 +            Some((ref mut current, ref mut child)) if *current < data => child.ordered_insert(data),
 +            _ => self.push_front(data),
 +        }
 +    }
  }

  fn main() {
      let mut ll = LinkedList::new();
      ll.push_front(1);
      ll.push_back(2);
      ll.push_back(4);
 +    ll.ordered_insert(3);

      println!("ll = {:?}", ll);
  }


Committing src/main.rs before applying edits.
Commit facbece Add src/main.rs file.
Applied edit to src/main.rs
Commit 14de829 aider: Add a method ordered_insert.
───────────────────────────────────────────────────────────────────────
Aider shows us the diff between the existing code and the new code, then commits this code to git using the relevant part of the chat as the commit message.

Let’s have a look at the edited file:
#[derive(Debug)]
#[derive(PartialEq)]
pub struct LinkedList<T>(Option<(T, Box<LinkedList<T>>)>);

impl<T:PartialOrd> LinkedList<T>{
    pub fn new() -> Self{
        LinkedList(None)
    }

    pub fn push_front(&mut self, data: T){
        let t = self.0.take();
        self.0 = Some((data, Box::new(LinkedList(t))));
    }

    pub fn push_back(&mut self, data:T) {
        match self.0 {
            Some((_, ref mut child)) => child.push_back(data),
            None => self.push_front(data),
        }
    }

    pub fn ordered_insert(&mut self, data: T) {
        match self.0 {
            Some((ref mut current, ref mut child)) if *current < data => child.ordered_insert(data),
            _ => self.push_front(data),
        }
    }
}

fn main() {
    let mut ll = LinkedList::new();
    ll.push_front(1);
    ll.push_back(2);
    ll.push_back(4);
    ll.ordered_insert(3);

    println!("ll = {:?}", ll);
}
We see that the method has been added and it looks OK, also, Aider has created code in main that allows us to easily check the new code.

Let’s build and run it:
➜ cargo run
   Compiling linked-list v0.1.0 (/Users/nm/projects/blogs/aider/linked-list)
    Finished dev [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/linked-list`
ll = LinkedList(Some((1, LinkedList(Some((2, LinkedList(Some((3, LinkedList(Some((4, LinkedList(None)))))))))))))
It works, the new element is inserted in its correct position in the linked list! Aider has created the ordered insert method correctly. It just looked at the existing code and the method name to figure out what code it should create.

Next, let’s ask Aider to write code that allows us to reverse the list:
> add a reverse method


 --- src/main.rs
 +++ src/main.rs
 @@ ... @@
  impl<T:PartialOrd> LinkedList<T>{
      pub fn new() -> Self{
          LinkedList(None)
      }

      pub fn push_front(&mut self, data: T){
          let t = self.0.take();
          self.0 = Some((data, Box::new(LinkedList(t))));
      }

      pub fn push_back(&mut self, data:T) {
          match self.0 {
              Some((_, ref mut child)) => child.push_back(data),
              None => self.push_front(data),
          }
      }

      pub fn ordered_insert(&mut self, data: T) {
          match self.0 {
              Some((ref mut current, ref mut child)) if *current < data => child.ordered_insert(data),
              _ => self.push_front(data),
          }
      }
 +
 +    pub fn reverse(&mut self) {
 +        let mut prev = None;
 +        let mut current = self.0.take();
 +        while let Some((data, mut next)) = current {
 +            current = next.0.take();
 +            next.0 = prev;
 +            prev = Some((data, next));
 +        }
 +        self.0 = prev;
 +    }
  }

  fn main() {
      let mut ll = LinkedList::new();
      ll.push_front(1);
      ll.push_back(2);
      ll.push_back(4);
      ll.ordered_insert(3);
 +    ll.reverse();

      println!("ll = {:?}", ll);
  }


Applied edit to src/main.rs
Commit 63aa74f aider: Add a reverse method.
The code in the diff looks OK, we again see that Aider has thoughtfully added code that allows us to check the new reverse method. Let’s skip checking the file and just run it:
➜ cargo run
   Compiling linked-list v0.1.0 (/Users/nm/projects/blogs/aider/linked-list)
    Finished dev [unoptimized + debuginfo] target(s) in 1.39s
     Running `target/debug/linked-list`
ll = LinkedList(Some((4, LinkedList(Some((3, LinkedList(Some((2, LinkedList(Some((1, LinkedList(None)))))))))))))
Our linked list has been successfully reversed. So far, so good.

Unit Tests

Programming purists will be unhappy about how we are verifying our code. We should be using unit tests! Let’s add some test code:
 > add a tests module after the `LinkedList` impl
 ...

 +#[cfg(test)]
 +mod tests {
 +    use super::*;
 +
 +    #[test]
 +    fn test_push_front() {
 +        let mut ll = LinkedList::new();
 +        ll.push_front(1);
 +        assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(None))))));
 +    }
 +
 +    #[test]
 +    fn test_push_back() {
 +        let mut ll = LinkedList::new();
 +        ll.push_back(1);
 +        assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(None))))));
 +    }
 +
 +    #[test]
 +    fn test_ordered_insert() {
 +        let mut ll = LinkedList::new();
 +        ll.ordered_insert(2);
 +        ll.ordered_insert(1);
 +        assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
 +    }
 +
 +    #[test]
 +    fn test_reverse() {
 +        let mut ll = LinkedList::new();
 +        ll.push_back(1);
 +        ll.push_back(2);
 +        ll.reverse();
 +        assert_eq!(ll, LinkedList(Some((2, Box::new(LinkedList(Some((1, Box::new(LinkedList(None))))))))));
 +    }
 +}

  fn main() {
      let mut ll = LinkedList::new();


Applied edit to src/main.rs
Commit 842de71 aider: Add a tests module after the `LinkedList` implementation.
 

This code looks OK, let’s run the tests:
➜ cargo test
   Compiling linked-list v0.1.0 (/Users/nm/projects/blogs/aider/linked-list)
    Finished test [unoptimized + debuginfo] target(s) in 0.40s
     Running unittests src/main.rs (target/debug/deps/linked_list-046556eed9cc58ca)

running 4 tests
test tests::test_ordered_insert ... ok
test tests::test_push_back ... ok
test tests::test_push_front ... ok
test tests::test_reverse ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
All tests pass, nice work Aider!

Note that the tests test_push_front and test_push_back, don’t fully test the logic.
If we call push_front(1), push_front(2) in that order we would expect the order of the elements to be [2,1]
Let’s fix this:
> `push_back` and `push_front` need to be tested with more than one element to fully test


 --- src/main.rs
 +++ src/main.rs
 @@ ... @@
  #[cfg(test)]
  mod tests {
      use super::*;

      #[test]
      fn test_push_front() {
 -        let mut ll = LinkedList::new();
 -        ll.push_front(1);
 -        assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(None))))));
 +        let mut list = LinkedList::new();
 +        list.push_front(2);
 +        list.push_front(1);
 +        assert_eq!(list, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
      }

      #[test]
      fn test_push_back() {
 -        let mut ll = LinkedList::new();
 -        ll.push_back(1);
 -        assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(None))))));
 +        let mut list = LinkedList::new();
 +        list.push_back(1);
 +        list.push_back(2);
 +        assert_eq!(list, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
      }

      #[test]
      fn test_ordered_insert() {
          let mut ll = LinkedList::new();
          ll.ordered_insert(2);
          ll.ordered_insert(1);
          assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
      }

      #[test]
      fn test_reverse() {
          let mut ll = LinkedList::new();
          ll.push_back(1);
          ll.push_back(2);
          ll.reverse();
          assert_eq!(ll, LinkedList(Some((2, Box::new(LinkedList(Some((1, Box::new(LinkedList(None))))))))));
      }
  }


Applied edit to src/main.rs
Commit 474a0bf aider: The `push_back` and `push_front` methods have been tested with more than one element.
 

Looks good, let’s run the tests:
❯ cargo test
   Compiling linked-list v0.1.0 (/Users/nm/projects/blogs/aider/linked-list)
    Finished test [unoptimized + debuginfo] target(s) in 0.41s
     Running unittests src/main.rs (target/debug/deps/linked_list-046556eed9cc58ca)

running 4 tests
test tests::test_reverse ... ok
test tests::test_ordered_insert ... ok
test tests::test_push_back ... ok
test tests::test_push_front ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
 

Below is the final version of main.rs:
#[derive(Debug)]
#[derive(PartialEq)]
pub struct LinkedList<T>(Option<(T, Box<LinkedList<T>>)>);

impl<T:PartialOrd> LinkedList<T>{
    pub fn new() -> Self{
        LinkedList(None)
    }

    pub fn push_front(&mut self, data: T){
        let t = self.0.take();
        self.0 = Some((data, Box::new(LinkedList(t))));
    }

    pub fn push_back(&mut self, data:T) {
        match self.0 {
            Some((_, ref mut child)) => child.push_back(data),
            None => self.push_front(data),
        }
    }

    pub fn ordered_insert(&mut self, data: T) {
        match self.0 {
            Some((ref mut current, ref mut child)) if *current < data => child.ordered_insert(data),
            _ => self.push_front(data),
        }
    }

    pub fn reverse(&mut self) {
        let mut prev = None;
        let mut current = self.0.take();
        while let Some((data, mut next)) = current {
            current = next.0.take();
            next.0 = prev;
            prev = Some((data, next));
        }
        self.0 = prev;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_push_front() {
        let mut list = LinkedList::new();
        list.push_front(2);
        list.push_front(1);
        assert_eq!(list, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
    }

    #[test]
    fn test_push_back() {
        let mut list = LinkedList::new();
        list.push_back(1);
        list.push_back(2);
        assert_eq!(list, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
    }

    #[test]
    fn test_ordered_insert() {
        let mut ll = LinkedList::new();
        ll.ordered_insert(2);
        ll.ordered_insert(1);
        assert_eq!(ll, LinkedList(Some((1, Box::new(LinkedList(Some((2, Box::new(LinkedList(None))))))))));
    }

    #[test]
    fn test_reverse() {
        let mut ll = LinkedList::new();
        ll.push_back(1);
        ll.push_back(2);
        ll.reverse();
        assert_eq!(ll, LinkedList(Some((2, Box::new(LinkedList(Some((1, Box::new(LinkedList(None))))))))));
    }
}

fn main() {
    let mut ll = LinkedList::new();
    ll.push_front(1);
    ll.push_back(2);
    ll.push_back(4);
    ll.ordered_insert(3);
    ll.reverse();

    println!("ll = {:?}", ll);
}
This ends our little demo of Aider.

Coding Demo Summary

We started off setting up and upgrading Aider, and adding the necessary files (main.rs) to Aider. Then we asked Aider to create new Rust code for a linked-list implementation. We were able to verify this functionality using code in the main method that Aider also added for us. Then we asked Aider to create unit tests to test the implementation. These tests ran for the first time. Then we asked Aider to more fully test the push_forward, push_back methods. We also were able to run these tests without problem the first time. Note that this code is intermediate-level Rust code and Aider, backed by GTP-4 Turbo, handled its creation and testing without any problems.

 

Handling Errors

There are many other useful commands such as being able to run or build programs and have the output passed to Aider:
/run cargo run
If this fails, Aider will automatically try to make the required changes to the code to fix it!

 

Repo Maps

Aider’s recent enhancements significantly improve its handling of large projects. Repo Maps are a key feature, allowing Aider to comprehend the entire repository structure, not just the files directly added to it. These maps, when processed by the Language Learning Model (LLM), enable Aider to suggest relevant files that should be included for more comprehensive analysis. This selective inclusion strategy ensures a thorough understanding of the repository while efficiently managing token consumption, crucial for processing extensive projects without overburdening the AI.

For more detailed information, visit Aider’s documentation on Repo Maps: https://aider.chat/docs/repomap.html

Chat History

Aider records its chat history in markdown format in the .aider.chat.history.md file. You can open this in a markdown viewer to get access to all previous sessions.

What to do when exceeding maximum tokens

You may occasionally get a max token exceeded message. This usually happens when the current session has accumulated a long history or you have added too many files. In either case you may view your current token allocation with:
> /tokens

Approximate context window usage, in tokens:

$ 0.01 1,302 system messages
$ 0.02 1,986 chat history use /clear to clear
$ 0.01 604 src/main.rs use /drop to drop from chat
================
$ 0.04 3,892 tokens total
127,180 tokens remaining in context window
131,072 tokens max context window size
 

You can then clear the current session’s chat history with:
> /clear
or drop some of the added files with:
src/main.rs src/sort.rs
> /drop src/sort.rs

Removed src/sort.rs from the chat
───────────────────────────────────────────────────────────────────────
src/main.rs
I have not seen a token exceeded error using GTP-4 Turbo, due to the large 128k token limit.

Of course, you can also combine GitHub CoPilot and Aider for even more assistance!

 

Summary

 

In summary, Aider is a great coding tool for eliminating many of the more repetitive and mundane coding tasks. The integration with the command line and automation of standard coding tasks is very well thought out. I recommend using Aider after understanding roughly what you want to write and then proceeding in small increments as suggested by the docs and shown in the above demo.

Aider is like having a junior programmer at your disposal, who never gets tired and has endless patience.

 

 

References

Aider GitHub: https://github.com/paul-gauthier/

Nice unit test demo on YouTube: https://www.youtube.com/watch?v=AeASAsPp9LE&list=PLRkN7vCqcLUyb9QDt0Qr7brm6OK1G2bNJ&index=1

 

次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事