I have a pet project I started around two years ago to get my head and hands around the AI tooling. It is a chatbot in a Telegram group chat, providing several chat-specific features. It’s nothing serious, just a way not to become rusty and try out some crazy things I did not have the opportunity to try at work.
The project went through several rewrites and refactorings (as it always happens with long-lasting pet projects), changed several technologies, adopted clean architecture, an inversion-of-control framework, and all of those lovely things happened when there was no product pressure and close-to-non-existing customer needs.
One of the things that was there from the very beginning is the LangChain library to build agents. It fueled conversation management, prompt passing, features like URL summarization, and some other pieces of the infrastructure. The main reason to choose was, of course, the hype around it. Everyone seemed to be using it, the library was developing at a crazy pace, and it seemed like a good idea to have skills with it in your portfolio.
I’ve worked around it for 2 and a half years, building features around it, and recently made a decision to drop it from the project entirely. How come? LangChain, for all this time, was a huge pain in the ass: I had to fight the library way more than I had to actually build something around it due to several factors.
The first of them is just how monstrous its abstractions are. The library fell victim to being the first major success in the field and had to quickly adapt to the arising needs and changes in the API of the largest model providers. To solve it, the designers of the library made a decision to abstract away every single building block of the agentic infrastructure and tie them in the most obscure way possible. When developing with the LangChain, you have to constantly jump around complex class hierarchies, trying to understand how to solve the problem you have, and you juggle class hierarchies back and forth. When what you need is a simple template string, a couple of dicts, and a function call, you’re going to have multiple internal classes involved, understand the whole class Runnable’s composition, learn the DSL, and make it somehow stick well in the existing architecture.
Those abstractions also come at the cost of a very complex debugging, when you’re stuck in the loop of trying to understand why the prompt behaved that way or not the other you’re not only oblidged to work with the unstable nature of the generative AI, but to be prepared to trace those nasty interface interactions the library chose to use, already in the part of the complex architecture. The inner desire to keep things simple inside went totally south.
On top of that, the abstractions are designed in a way that, on its own, the langchain project is not enough, and it requires multiple packages on top of it to somehow work. The project seems to have gone the Enterprise
way and undergone several project splits in an attempt to bring capital to the project, and now provides its own cloud capabilities, another framework for building agent graphs, and a myriad of packages and integrations swelling your dependency graph.
A lot of these things happened at the developer experience expense, and you had to follow multiple API changes to make it work. And there’s LangChain v1.0 on the horizon, so all of your code you’ve been building for years is now shining with deprecation warnings and would require you to rewrite the project from scratch once again.
Given all that, the documentation state is worse than the SQLAlchemy one (which is difficult to contend). There are so many auto-generated docs, concepts, deprecation warnings, and a necessity to support multiple major versions of the framework (as well as sync/async approaches), so that it makes it extremely challenging to navigate and actually build something on top of it. A year ago, the initiative to revamp the docs took place, but even after that, going through the docs feels like the route you’d better not take.
At some point I’ve decided I had had enough. With multiple additions of abstractions to an already complex set of abstractions I was fighting a battle with the framework that was supposed to help me, and I’ve decided to move on. Given the project was more or less abstracted from the langchain machinery (by need, not by choice), I’ve taken a day and rewritten the functionality to the OpenAI agents SDK, mostly because I’ve already been using primarily the OpenAI API and it seemed easier and skinnier.
I felt quite relieved. I’ve dropped multiple subdependencies, and the code became way simpler and easier to read and follow. There are several alternatives, of course, that could have been tried out (LlamaIndex, Atomic Agents, to name a few), but I fell for something that felt the easiest to deal with.
So, what are my afterthoughts on the topic? If you’re only starting to build some agents, take a look at some different tooling, something that you take a look at the docs, and you very quickly get the idea of how to work with it. Or do not take a framework at all, take some abstraction over the API (litellm?) and build what you need around it yourself, it’s really not that difficult for beginner / intermediate ideas. Or if you really want to jump on the LangChain train, wait for v1.0 stable release, or use the langgraph for your needs.