Most LangChain and LlamaIndex errors people ask about have nothing to do with the LLM itself — they're packaging problems and state-mismatch problems. The ecosystem restructured its packaging significantly, splitting what used to be one big langchain install into a core package plus dozens of provider-specific ones, and that single change accounts for a large share of the errors developers hit switching between tutorials written at different points in that transition.
from langchain.chat_models import ChatOpenAI # deprecated path
llm = ChatOpenAI(model="gpt-4o")
ModuleNotFoundError: No module named 'langchain_openai'
Every provider integration that used to live inside the core langchain package now ships as its own pip package — langchain-openai, langchain-anthropic, langchain-community for the long tail of community-maintained integrations, and so on. A tutorial or an old codebase importing from langchain.chat_models is targeting a layout that no longer exists in current releases.
pip install -U langchain-openai
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
The same fix pattern applies to langchain_community imports (vector stores, document loaders, and other integrations that don't have their own dedicated package): install langchain-community separately, then import from it directly rather than through the old top-level namespace. If you're maintaining a codebase that spans this transition, pin langchain-core explicitly — it's the shared abstraction layer every integration package version-checks against, and a mismatch between langchain-core and an integration package produces import errors that look identical to this one but need a version bump instead of a fresh install.
pydantic.error_wrappers.ValidationError: 1 validation error for LLMChain
llm
instance of Runnable expected (type=type_error.arbitrary_type; expected_arbitrary_type=Runnable)
This one shows up when older chain-construction code (building an LLMChain by passing a plain LLM object into a constructor) meets the current LangChain Expression Language, where nearly everything — chat models, prompt templates, output parsers, retrievers — implements a common Runnable interface. The constructor-style API still exists in places, but the type it expects has changed underneath it.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_template("Summarize: {text}")
llm = ChatOpenAI(model="gpt-4o")
parser = StrOutputParser()
chain = prompt | llm | parser
result = chain.invoke({"text": "..."})
The | operator is doing real work here, not just syntax sugar — it composes Runnable objects into a pipeline where each stage's output feeds the next stage's input, with streaming, batching, and async all handled consistently across every stage. If you're working from an older tutorial that constructs chains via class constructors, treat that as a signal the example predates LCEL rather than something to work around — the composition syntax is a straight replacement, not an alternative API to reconcile with the old one.
from llama_index.core import VectorStoreIndex, StorageContext, load_index_from_storage
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
query_engine = index.as_query_engine()
response = query_engine.query("What does the document say about pricing?")
InvalidDimensionException: Dimensionality of query embeddings (1536) does not match
index dimensionality (768)
A vector store doesn't record which embedding model built it — it just stores vectors of whatever size arrived first. If the index was built with a 768-dimension model and the query engine is now configured with a 1536-dimension model (a different model entirely, or the same OpenAI embedding family with a different explicit dimensions setting), nothing catches the mismatch until the actual similarity search tries to compare vectors of two different sizes. It's a state-tracking problem more than a code bug — the two ends of the pipeline just disagree about what "the embedding model" currently is.
from llama_index.core import Settings
from llama_index.embeddings.openai import OpenAIEmbedding
# Explicit and pinned, not left to whatever the default happens to be
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small", dimensions=768)
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context) # now consistent with what built it
The durable fix is to stop treating the embedding model as ambient global state. Record the exact model name and dimension alongside the persisted index — a small metadata file next to the storage directory works fine — and check it explicitly before querying rather than trusting whatever Settings.embed_model happens to default to in the environment you're running from. This class of bug is invisible in a single-developer notebook where the default never changes, and shows up almost exclusively when an index gets shared across environments or rebuilt months later with an updated default model.