Skip to content

Upgrade to LangChain 1.x - #195

Merged
adamjohnwright merged 2 commits into
mainfrom
deps/langchain-1x
Sep 9, 2026
Merged

adamjohnwright merged 2 commits into
mainfrom
deps/langchain-1x

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

The upgrade the retriever rewrite existed to unblock. 69 advisories → 44; the whole LangChain family is now clean.

langchain 0.3.30 → 1.4.0
langchain-core 0.3.86 → 1.6.2
langchain-openai 0.2.14 → 1.6.1
langgraph 0.2.76 → 1.2.11
openai 1.109 → 2.54.0
ragas 0.2.15 → 0.4.3

Three things did NOT move

chromadb stays below 1.0 (and langchain-chroma at 0.2.x with it). langchain-chroma 1.x requires chromadb>=1.3.5, and chromadb 1.x opens a 0.5/0.6 bundle by migrating its sqlite in place (sysdb 9 → 10) — which needs write access. Installed bundles are root-owned 755, so running as a normal user gives, before a single question:

InternalError: error returned from database: (code: 8) attempt to write a readonly database

That is a deliberate operational step — chown every bundle on every host — not something to slip into a dependency upgrade. The migration itself is sound: I verified chromadb 1.5.9 reads a 0.6-written bundle, and that 0.6.3 still reads it afterwards, so a rollback does not strand data. Whenever you want it, it is a one-line change plus a chown.

langchain-community stays at 0.3.31. 0.4 drops langchain_community.chat_models.vertexai, which ragas 0.4.3 still imports unconditionally — so 0.4 makes import ragas fail and takes ./bin/evaluate with it. ragas declares langchain-community with no version bound, so the resolver cannot see the conflict; only importing can.

torch and transformers stay as they were, per #194.

Code the upgrade forced

  • langchain.chains.* → langchain-classic, their supported home rather than a shim. Rewriting the RAG chain as LCEL is a behaviour change and does not belong here.
  • langgraph.utils.runnable.RunnableLike is gone, and langgraph 1.0 matches nodes structurally on the parameter name state. The node contract is now a Protocol here, and no_search’s parameter is named rather than _.
  • EnsembleRetriever now validates len(weights) == len(retrievers), so the empty-ensemble trick used to reach weighted_reciprocal_rank raises. That trick was removed from the pipeline by the rewrite and survived only in the test comparing against it — the upgrade breaking it is the argument for the rewrite, restated. The vendored RRF still matches LangChain 1.x exactly over 2000 random inputs.

Two bugs found while doing it

  • AgentGraph.__del__ read self.pool, which does not exist when __init__ raised part way through. A chromadb startup error therefore surfaced as AttributeError: 'AgentGraph' object has no attribute 'pool' while the real cause went unprinted.
  • test_the_vector_side_makes_no_llm_call asserted on the literal string "from langchain.retrievers.self_query". Under 1.x that path is langchain_classic.retrievers.self_query — the test would have gone green while the thing it guards was reintroduced.

Deleted: src/evaluation/test_generator.py

It targeted the ragas 0.1 API — from_langchain(generator_llm=, critic_llm=), generate_with_langchain_docs(test_size=, distributions=) — none of which exists in 0.4. Its own TODO had said so since 0.2, and nothing imported it. It had not run for three major versions. Recoverable from git; the README says what rebuilding it would be for.

Measured, not assumed

bin/retrieval_baseline before and after, 20 questions × 4 collections × 2 retrievers:

2/160 retriever results changed.

Both are adjacent transpositions on the vector side — the run-to-run ANN variance the harness already documents (plain vector is byte-identical on 78/80 between two identical runs). BM25 is unchanged.

Plus: the chainlit app imports and registers its hooks, and the chain answers a real question through both invoke and ainvoke. 187 tests pass.

The upgrade the retriever rewrite existed to unblock. 69 advisories
down to 44; the whole LangChain family is now clean.

    langchain           0.3.30 -> 1.4.0
    langchain-core      0.3.86 -> 1.6.2
    langchain-openai    0.2.14 -> 1.6.1
    langgraph           0.2.76 -> 1.2.11
    langgraph-checkpoint 2.1.2 -> 4.2.0
    openai              1.109  -> 2.54.0
    ragas               0.2.15 -> 0.4.3

Three things did NOT move, each for a reason.

chromadb stays below 1.0, and langchain-chroma at 0.2.x with it.
langchain-chroma 1.x requires chromadb>=1.3.5, and chromadb 1.x opens a
0.5/0.6 bundle by migrating its sqlite sysdb in place (9 -> 10), which
needs write access to the bundle. Installed bundles are root-owned 755,
so running as a normal user gives

    InternalError: error returned from database: (code: 8)
    attempt to write a readonly database

at startup, before a single question. That is a deliberate operational
step -- chown every bundle on every host -- not something to slip into
a dependency upgrade. The migration itself is sound: chromadb 1.5.9
reads a 0.6-written bundle, and 0.6.3 still reads it afterwards, so a
rollback does not strand the data. langchain-chroma 0.2.3 declares
langchain-core>=0.3.52 with no upper bound and was verified to run
against core 1.6.2 through the full chain.

langchain-community stays at 0.3.31. 0.4 drops
langchain_community.chat_models.vertexai, which ragas 0.4.3 still
imports unconditionally, so 0.4 makes `import ragas` fail and takes
./bin/evaluate with it. ragas declares langchain-community with no
version bound, so the resolver cannot see it; only importing can.
0.3.31 accepts langchain-core <2.0.0.

torch and transformers stay as they were, per the previous upgrade.

Code changes the upgrade forced:

- langchain.chains.* moved to langchain-classic, which is their
  supported home rather than a shim. Rewriting the RAG chain as LCEL is
  a behaviour change and does not belong here.
- langgraph.utils.runnable.RunnableLike is gone, and langgraph 1.0's
  add_node matches nodes structurally on the parameter name `state`.
  The node contract is now a Protocol in this repository, and
  no_search's parameter is named rather than `_`.
- EnsembleRetriever now validates len(weights) == len(retrievers), so
  the empty-ensemble trick used to reach weighted_reciprocal_rank
  raises. That trick was removed from the pipeline by the rewrite and
  survived only in the test comparing against it -- the upgrade
  breaking it is the argument for the rewrite, restated. The vendored
  RRF still matches LangChain 1.x exactly, over 2000 random inputs.
- ragas 0.4 types evaluate() as EvaluationResult | Executor; checked
  rather than cast, so a future default change says so.

Two bugs found while doing it:

- AgentGraph.__del__ read self.pool, which does not exist when
  __init__ raised part way through. A chromadb startup error therefore
  surfaced as "AttributeError: 'AgentGraph' object has no attribute
  'pool'" while the real cause went unprinted.
- test_the_vector_side_makes_no_llm_call asserted on the literal string
  "from langchain.retrievers.self_query". Under 1.x that path is
  langchain_classic.retrievers.self_query, so the test would have gone
  green while the thing it guards was reintroduced. It now matches
  import lines on the module tail.

src/evaluation/test_generator.py is deleted. It targeted the ragas 0.1
API -- from_langchain(generator_llm=, critic_llm=),
generate_with_langchain_docs(test_size=, distributions=) -- none of
which exists in 0.4, its own TODO had said so since 0.2, and nothing
imported it. It had not run for three major versions.

Measured, not assumed: bin/retrieval_baseline before and after over 20
questions x 4 collections x 2 retrievers shows 2/160 results changed,
both adjacent transpositions on the vector side, which is the run-to-run
ANN variance the harness already documents (plain vector is
byte-identical on 78/80 between two identical runs). BM25 is unchanged.
The chainlit app imports and registers its hooks; the chain answers a
real question through both invoke and ainvoke.
CI could not import langchain_classic; locally everything passed. The
package was in my virtualenv as a leftover from langchain-community
0.4, which was installed for a few minutes while working out that ragas
could not tolerate it. `poetry install` does not prune, so it stayed --
and made code that imports langchain_classic directly look fine against
a lock that never contained it.

Both packages are imported by name here (rag_chain and the
metadata_info modules; RecursiveCharacterTextSplitter in
data_generation), so both are now direct dependencies. Declaring what
you import is right regardless of how it used to arrive.

Verified against a virtualenv synced to the lock with --sync, which
removed twelve stale packages, including the two this commit adds back
deliberately. Gates, the CI import check and the real chain all pass on
that clean tree.
@adamjohnwright
adamjohnwright merged commit 80b29fa into main Sep 9, 2026
10 checks passed
@adamjohnwright
adamjohnwright deleted the deps/langchain-1x branch September 9, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant