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