This is the fifth part of the Refactoring BabyAGI series.
Part 1: Code Review - planning the refactoring and identifying issues
Part 2: Clean Architecture - establishing the general structure
Part 3: Better service classes and test doubles - decouple from external services and replace them with mocks.
Part 4: Preparing to introduce the Task class and first steps
So just to repeat, this is what the Task class looks like:
Let’s see how the run() function changed:
Before:
After:
The main change is that the last three lines are much simpler. This is a clear sign that we are going in the right direction. This class “wants to exist”.
But all these functions interface must change first the VectorDB (both of them):
PineCone
Before:
After:
Nothing too interesting. We store the entire class in the metadata field and extract the relevant parts to the DB. Then at query time, we reconstruct the Task class.
LanceDB
Before:
After:
Same deal as before. I use the ** operator and the __dict__ built-in property to access its fields. I slightly rewrote the DB schema, but because this is internal to the Adapter, this doesn’t affect the rest of the codebase—a clear benefit of the refactoring (and the Adapter Pattern and the Clean Architecture).
There are only two agents left, so here are both of them at once:
Before:
After:
There are not too many changes here either because of the previous refactorings. Only in task_creation_agent the dictionary was changed to a constructor. And the same for the return value of the to_task() private function.
Let’s take a look at the new run() function.
What does it do in the loop?
Execute top task
Create new tasks
Prioritise tasks
These are better names than the “agents”. We talk about agents if they have their own policy, but now they are too weak to be called that. But we should extract the “execute top task” concept.
One small change: Part of the code only runs if the list has elements (there are still tasks left), but if there are none left, you should exit because there is no point in doing the other two tasks.
This should be turned into a “Guard clause”,“ an exceptional case. This is a basic readability solution that special situations are dealt with at the beginning of the function, so if someone is checking “what happens if the input is zero, or null or an empty list,” they don’t need to read the entire function because it will be at the top.
On the other hand, if someone needs to understand how the entire function works, they only need to read downward, not sideways, a jagged program. This is called “Happy path to the left” and another readability solution.
Summary
There can be a lot of other changes to this, but I finish it at this point because I think at this stage, it is more important to think about what’s next than cosmetics.
The two primary goals were:
Establish Clean Architecture (by Inversion of Control and decoupling through Adaptern Patterns)
Introduce the Task class (as a very simple domain model).
Both goals were achieved. The first time I did this, it took me about 2.5 hours. If you work on a codebase for more than a couple of days, it is almost certainly a good idea to do a refactoring to this level. It is much easier to do new things and express your intentions in code if it is moved to this level.
[Edit]
The entire series:
Part 1: Code Review - planning the refactoring and identifying issues
Part 2: Clean Architecture - establishing the general structure
Part 3: Better service classes and test doubles - decouple from external services and replace them with mocks.
Part 4: Preparing to introduce the Task class and first steps
Part 5 (this): Finishing the refactoring of the Task class
This is the end of the series. Please subscribe if you would like to be notified about similar content: