Lesson 4: The course stack: every tool, one question
Module 1 · Lesson 4 · About 45 minutes · Needs: Python basics and a Groq or Gemini key (course-guide/02); no earlier lesson's code
The situation
Before anyone writes ingestion code, Kwame asks: which tools? His prototype used whatever the first tutorial used, and he could not say which parts sent Hollin's documents somewhere else.
This lesson lays out the course's stack and runs every piece once on Ellie Shaw's question: "Is a repeated E7 motor overheating fault on the X200 blender covered by the warranty?" The Hollin data is simulated.
A real case, first
On 14 July 2023 Max Woolf, a former senior data scientist at BuzzFeed, published "The Problem With LangChain" (minimaxir.com, 2023). At BuzzFeed he had built a recipe chatbot for the Tasty brand: recipes "converted to embeddings and saved in a vector store", the nearest ones "fed to ChatGPT as added context". That is retrieval-augmented generation, and LangChain was "by-far the popular tool of choice for RAG".
It went badly. "I wasted a month learning and testing LangChain." The team "went back to a lower-level ReAct flow, which immediately outperformed my LangChain implementation in conversation quality and accuracy." His summary: it "makes simple things relatively complex", and its "prompt engineering is just f-strings". This is one engineer's account of LangChain in 2023; LangChain has since reached version 1.0. The lesson for this course still holds: build each step in plain Python first, so you know what a framework is doing for you.
What you will be able to do
- Name the tool for each step of Hollin Answers and say what runs locally.
- Set up keys safely and switch model provider by changing one word.
- Embed, rerank and store Hollin's documents with local open models.
- Spot a mismatched embedding model that fails without an error.
- Build the same retrieval in plain Python and in LangChain, and read the prompt each sends.
1. Course stack at a glance (Essential)
You will meet a dozen libraries. Knowing which step each serves stops them blurring together.
A RAG system is a chain of steps: load, chunk, embed, store, search, rerank, answer. Each has one tool in this course, chosen because it is open source and runs in Colab. Modules 2 to 12 build each step properly; this lesson runs a small version of most of them once.
The important line is the one around your notebook. Embedding, reranking and storage run on Colab's processor; only the answer step sends text to a provider. Farah, who asks what leaves the building, now has a one-line answer.
The libraries beyond the setup cell are pinned: installed at an exact version that was checked on PyPI, so a lesson behaves the same next month. The setup cell's own libraries take the latest version, because provider connectors must keep up with the providers. This lesson needs three extras.
%pip install -q -U langchain langgraph langchain-groq langchain-google-genai langchain-ollama%pip install -q sentence-transformers==6.1.0 chromadb==1.5.9 langchain-huggingface==1.2.3Code walkthrough
- The first cell is the standard install;
-Utakes the newest versions. The second pins this lesson's extras from course-guide/02.
2. Project setup and environment keys (Essential)
An API key is a password that spends money. Most leaked keys are leaked by accident, pasted into a notebook that someone later shares.
In Colab the key lives in the Secrets panel, named exactly GROQ_API_KEY or GOOGLE_API_KEY; on your own computer, in a .env file that Git ignores. Either way the code reads it from an environment variable, a named value handed to the running program. The key never appears in a cell.
The setup cell is the same in every lesson that calls a model. One word, PROVIDER, picks the model name and the key name; the cell copies the key into the environment and builds llm.
import os
from langchain.chat_models import init_chat_model
PROVIDER = "groq" # "groq" or "gemini" in Colab; "ollama" on your own computer
MODELS = {"groq": "groq:openai/gpt-oss-20b", "gemini": "google_genai:gemini-3.5-flash-lite", "ollama": "ollama:llama3.2:3b"}
KEY = {"groq": "GROQ_API_KEY", "gemini": "GOOGLE_API_KEY", "ollama": None}[PROVIDER]
if KEY and KEY not in os.environ:
from google.colab import userdata # keys live in Colab's Secrets panel
os.environ[KEY] = userdata.get(KEY)
llm = init_chat_model(MODELS[PROVIDER], temperature=0, max_retries=6)Code walkthrough
MODELSis the one place model names appear. Both were listed by their providers on 12 October 2026:openai/gpt-oss-20bas a Groq production model,gemini-3.5-flash-liteas a stable Gemini model. Names change often; if one fails with "model not found", replace it here.userdata.get(KEY)reads the secret;os.environ[KEY] = ...puts it where the provider library looks.max_retries=6waits and retries when the provider says "too many requests".
Check what you have without printing the key:
from importlib.metadata import version
for name in ("GROQ_API_KEY", "GOOGLE_API_KEY"):
value = os.environ.get(name, "")
print(f"{name:15} {'set, ' + str(len(value)) + ' characters' if value else 'not set'}")
print("provider:", PROVIDER, "->", MODELS[PROVIDER])
for pkg in ("langchain", "langchain-groq", "langchain-google-genai", "sentence-transformers", "chromadb"):
print(f"{pkg:22} {version(pkg)}")Code walkthrough
- The loop reports each key as set or not set, and its length, never its value. In Colab only the chosen provider's key is set.
version(pkg)reads the installed version, so you can compare it with course-guide/02 when something behaves differently.
3. LLM providers behind one setting (Essential)
Providers change prices, limits and model names. If the provider is one setting, any change costs you one edited word.
init_chat_model reads the part of the model string before the colon (groq, google_genai, ollama) and builds the matching connector, the small library that speaks that provider's API. Every connector offers the same methods, so the rest of the lesson talks to llm and never names a provider again. Ollama runs the model on your own machine, so nothing leaves it.
Providers sometimes reject a reply even though your request was fine. The course wraps every model call in a guard: try once more, and after a second failure return no reply with a named reason, provider_error, which the next step handles instead of crashing.
def ask(model, messages):
"""Returns (reply, None), or (None, reason) after two refusals."""
for attempt in range(2): # one retry: the next reply is usually fine
try:
return model.invoke(messages), None
except Exception as err:
last = err
return None, f"provider_error: {type(last).__name__}"
reply, reason = ask(llm, "Reply with the single word: ready")
print(reply.text if reply else reason)Code walkthrough
model.invoke(messages)sends the request;exceptcatches a failure and the loop tries once more.- The last line prints the reply (look for
ready), or the reason if both attempts failed.
Closed models plug into the same line. The snippet below comes from LangChain's documentation; it needs paid keys, so it is not run here.
# not run here: needs paid keys. Call shape from https://docs.langchain.com/oss/python/langchain/models;
# model ids from https://developers.openai.com/api/docs/models and
# https://platform.claude.com/docs/en/about-claude/models/overview (all checked 12 October 2026)
# pip install -U "langchain[openai]" "langchain[anthropic]"
from langchain.chat_models import init_chat_model
llm = init_chat_model("openai:gpt-6-astra") # reads OPENAI_API_KEY
llm = init_chat_model("anthropic:claude-sonnet-5-5") # reads ANTHROPIC_API_KEYCode walkthrough
- Each provider needs its own connector package and its own key; the call that builds
llmis unchanged. - Ids checked 12 October 2026 on each provider's models page; they change often.
4. Embedding model options (Essential)
Retrieval quality starts with the embedding model, and changing it later means re-embedding everything. It is worth knowing what you picked and why.
The data: the date helper and the policies and manuals blocks from the shared data, 14 documents in all.
from datetime import date
COURSE_DATE = date(2026, 10, 9) # "today" in every lesson; never use date.today()
SNAPSHOT = date(2026, 10, 2) # the day the corpus below was exported
SOURCE = {"policy": "help-centre", "faq": "help-centre", "notice": "help-centre",
"manual": "manuals", "bulletin": "service-portal", "ticket": "ticket-system",
"partner": "partner-portal", "internal": "finance-share", "forum": "community-forum"}
AUTHORITY = {"policy": 1.0, "notice": 1.0, "manual": 0.9, "bulletin": 0.9, "partner": 0.8,
"internal": 0.8, "faq": 0.7, "ticket": 0.3, "forum": 0.1}
def doc(id, type, title, updated, text, acl=("public",), products=(), status="current", authority=None):
"""One document record. acl lists the groups allowed to see it."""
return {"id": id, "type": type, "title": title, "updated": updated, "text": text,
"acl": list(acl), "products": list(products), "status": status,
"source": SOURCE[type], "authority": AUTHORITY[type] if authority is None else authority}
DOCS = []Code walkthrough
doc(...)builds one record: ID, type, title, date, text, access list (acl) andstatus.
DOCS += [
doc("pol-warranty-2026", "policy", "Warranty terms (2026)", "2026-03-01",
"Every Hollin appliance has a 2-year warranty from the date of purchase. Blender motors on the X200 "
"and X300 Pro are covered for 5 years. The warranty covers manufacturing faults. It does not cover "
"blade wear, cracked jars, limescale damage or accidental damage. You need proof of purchase to claim."),
doc("pol-warranty-2024", "policy", "Warranty terms (2024, archived)", "2024-02-10",
"Every Hollin appliance has a 1-year warranty from the date of purchase. Blender motors are covered "
"for 2 years. The warranty covers manufacturing faults and cracked jars. You need proof of purchase "
"to claim.", status="archived"),
doc("pol-repairs", "policy", "Repairs and turnaround", "2026-05-12",
"We aim to repair and return an appliance within 10 working days of receiving it. Collection by "
"courier is free for appliances under warranty. Out-of-warranty repairs carry a £25 inspection fee, "
"which is deducted from the repair price if you go ahead."),
doc("pol-spare-parts", "policy", "Ordering spare parts", "2026-04-20",
"Spare parts are sold at parts.hollin.example. Standard delivery takes 3 to 5 working days and costs "
"£3.95. Delivery is free on parts orders over £30."),
doc("pol-privacy", "policy", "Your account data", "2026-02-01",
"You can download or delete your account data from Settings, then Privacy. Deletion requests are "
"completed within 30 days."),
doc("pol-registration", "policy", "Registering your appliance", "2025-11-02",
"Register your appliance at hollin.example/register so we can contact you about safety notices. "
"You do not need to register to claim under the warranty."),
]Code walkthrough
- Six policies.
pol-warranty-2026holds Ellie's answer;pol-warranty-2024is archived and says motors get 2 years.
DOCS += [
doc("man-x200-specs", "manual", "X200 Blender specifications", "2025-06-01",
"The X200 blender has a 1,200 W motor, a 2-litre Tritan jar and three speeds. High speed reaches "
"22,000 RPM. Model number HL-X200-UK.", products=["X200"]),
doc("man-x200-errors", "manual", "X200 error codes", "2025-06-01",
"E4 means the jar lid is not locked: twist the lid until it clicks. E7 means the motor has overheated: "
"unplug the blender for 20 minutes. E9 means a sensor fault: contact Hollin support.", products=["X200"]),
doc("man-x200-cleaning", "manual", "X200 cleaning and care", "2025-06-01",
"Wash the jar and blades in warm soapy water or in the dishwasher. Never put the motor base in water; "
"wipe it with a damp cloth.", products=["X200"]),
doc("man-x200-blades", "manual", "X200 blade assembly", "2025-06-01",
"A grinding noise, or food left in chunks, usually means the blades are worn. Replace the blade "
"assembly, part BLD-X200, about once a year with daily use.", products=["X200"]),
doc("man-x300-specs", "manual", "X300 Pro Blender specifications", "2026-04-10",
"The X300 Pro blender has a 1,500 W motor, a vacuum blending mode and ten speeds. High speed reaches "
"30,000 RPM. Model number HL-X300-UK.", products=["X300"]),
doc("man-x300-errors", "manual", "X300 Pro error codes", "2026-04-10",
"E4 means the vacuum lid seal is loose: take the seal out and press it back into its groove. E12 means "
"the vacuum pump has failed: contact Hollin support.", products=["X300"]),
doc("man-k50-kettle", "manual", "K50 Smart Kettle", "2025-09-09",
"The K50 smart kettle boils 1.7 litres in about four minutes and can keep water warm for up to two "
"hours. Descale it once a month with citric acid.", products=["K50"]),
doc("man-k50-app", "manual", "Pairing the K50 with the Hollin Home app", "2025-09-09",
"To pair the K50 with the Hollin Home app, hold the temperature dial down for 5 seconds until the ring "
"light flashes white, then choose Add appliance in the app.", products=["K50"]),
]
STANDALONE = "Is a repeated E7 motor overheating fault on the X200 blender covered by the warranty?"Code walkthrough
- Eight manual pages.
man-x200-errorssays E7 means the motor overheated: the other half of Ellie's answer. STANDALONEis Ellie's question, copied from the shared data.
An embedding model turns text into a list of numbers, a vector, so that texts with similar meaning get similar vectors. The course uses BAAI/bge-small-en-v1.5: small (about 130 MB), English, free, and run locally through sentence-transformers. Each text becomes 384 numbers, scaled so the vector has length 1.
Local open models run on your machine and need no key. Hosted models, such as Google's gemini-embedding-2 (gemini-embedding-2-preview on Google's models page, 12 October 2026) or OpenAI's text-embedding-3-small, need a key and every document goes out. Module 4 compares them. bge-small is the default because Hollin's text stays in the notebook.
from sentence_transformers import SentenceTransformer
embedder = SentenceTransformer("BAAI/bge-small-en-v1.5")
D = embedder.encode([d["text"] for d in DOCS], normalize_embeddings=True)
q = embedder.encode(STANDALONE, normalize_embeddings=True)
print("documents:", D.shape, " question:", q.shape, " first numbers:", q[:3].round(3))
scores = D @ q
top5 = scores.argsort()[::-1][:5]
for i in top5:
print(f"{scores[i]:.3f} {DOCS[i]['id']:18} {DOCS[i]['status']}")Code walkthrough
SentenceTransformer(...)downloads the model once and runs it on the CPU.normalize_embeddings=Truegives every vector length 1, soD @ q, one dot product per document, is the cosine similarity.argsort()[::-1][:5]takes the five highest scores. Shapes print as(14, 384)and(384,).
The current warranty comes first at 0.788 and the E7 page second at 0.741. The archived 2024 terms sit 4th at 0.725, very close behind. Similarity knows nothing about dates or status: that is Modules 5 and 6's work.
Break it: query with a different model
Documents and questions must be embedded by the same model. Swap only the question's model for another small one, all-MiniLM-L6-v2, which also makes 384 numbers.
other = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
q_wrong = other.encode(STANDALONE, normalize_embeddings=True)
wrong = D @ q_wrong # bge documents, MiniLM question: shapes still match
for i in wrong.argsort()[::-1][:5]:
print(f"{wrong[i]:.3f} {DOCS[i]['id']}")Code walkthrough
other.encode(...)makes a 384-number vector from a different model, soD @ q_wrongruns without any error.
Nothing failed, yet the numbers are meaningless: every score falls to about 0.2 (0.216 at the top), and pol-repairs enters the top 5. The two models put meaning in different places. A cut-off of 0.5 tuned for bge would now return nothing, which is why Module 4 stores the model name with every vector.
5. Cross-encoder rerankers from Hugging Face (Recommended)
Similarity search is fast because it compares vectors made separately. A slower second reader can reorder its few candidates.
A cross-encoder reads the question and one document together and returns one relevance score, so "covered" in the question can meet "covered for 5 years" in the document. It is too slow for a whole corpus, so it reranks only the top few. The course uses cross-encoder/ms-marco-MiniLM-L6-v2 from Hugging Face, about 90 MB.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2")
candidates = [DOCS[i] for i in top5]
ranked = reranker.rank(STANDALONE, [d["text"] for d in candidates])
for r in ranked:
print(f"{r['score']:6.2f} {candidates[r['corpus_id']]['id']}")Code walkthrough
candidatesare bge's top 5 documents.reranker.rank(question, texts)scores each pair and returns them best first;corpus_idpoints back intocandidates.
The reranker puts the E7 page first (3.48) and the 2026 terms second (3.25): together, exactly the two documents Ellie's answer needs. The archived terms drop to -0.35, and the two specification pages fall well below zero.
These scores are logits: raw numbers with no fixed range, not probabilities. The Sentence Transformers documentation says MS MARCO models "return logits rather than scores between 0 and 1". Only the order is reliable.
Break it: treat the score as a probability
q01 = "can I put the blender base in the sink" # labelled q01 in the shared data
cands = [DOCS[i] for i in (D @ embedder.encode(q01, normalize_embeddings=True)).argsort()[::-1][:5]]
best = reranker.rank(q01, [d["text"] for d in cands])[0]
print(cands[best["corpus_id"]]["id"], round(float(best["score"]), 2), "kept" if best["score"] > 0 else "dropped by a cut-off at 0")Code walkthrough
- The same retrieve-then-rerank, on another labelled question.
bestis the reranker's first choice.
The right page, man-x200-cleaning, comes first with -2.59. A cut-off at 0, picked because it "looks like" a probability boundary, would throw away a correct answer. Module 8 sets thresholds from measured questions.
6. Vector database options (Recommended)
With 14 documents a NumPy array is enough. Hollin has thousands of pages, several users and documents that change hourly, and that needs a store.
A vector store keeps vectors with their IDs and metadata, and finds the nearest ones to a query. This course uses Chroma and FAISS in notebooks (Module 5), and pgvector, the PostgreSQL extension, or Qdrant for the capstone service. Hosted vector databases are named only.
In Chroma each record is an ID, a vector, the text and metadata, extra fields such as status that a query can filter on. Passing your own vectors keeps bge-small in charge; otherwise Chroma would embed with its own default model.
import chromadb
client = chromadb.Client() # in memory: gone when the notebook stops
col = client.get_or_create_collection("hollin_docs", configuration={"hnsw": {"space": "cosine"}})
col.add(ids=[d["id"] for d in DOCS], embeddings=D.tolist(), documents=[d["text"] for d in DOCS],
metadatas=[{"status": d["status"], "type": d["type"]} for d in DOCS])
for where in (None, {"status": "current"}):
res = col.query(query_embeddings=[q.tolist()], n_results=4, where=where)
print(where, [(i, round(1 - dist, 3)) for i, dist in zip(res["ids"][0], res["distances"][0])])Code walkthrough
chromadb.Client()starts an in-memory store;{"space": "cosine"}makes distance equal to 1 minus cosine similarity.col.add(...)stores IDs, our vectors, texts and two metadata fields.where={"status": "current"}filters before ranking.1 - distturns distance back into similarity.
Without the filter, the top 4 match the NumPy scores and include the archived terms. With it, pol-warranty-2024 is gone and man-x300-specs takes 4th place.
7. Frameworks: plain Python, LangChain, LlamaIndex (Recommended)
Woolf lost a month to a framework he could not see into. The course's rule is plain Python first, then the framework, so you always know what it does for you.
LangChain offers one interface for models, embeddings, prompts and vector stores; the setup cell already uses it. LlamaIndex is built around indexing and querying documents. Both are open source. This course uses LangChain (and LangGraph for loops) and shows LlamaIndex as an example only.
First the plain version. The prompt is an f-string you can print, built from the reranker's top 2.
def build_prompt(question, docs):
sources = "\n".join(f"[{d['id']}] {d['text']}" for d in docs)
return ("Answer using only the sources below. Cite the source id in square brackets after each "
"claim. If the sources do not answer the question, say you do not know.\n\n"
f"Sources:\n{sources}\n\nQuestion: {question}")
top2 = [candidates[r["corpus_id"]] for r in ranked[:2]]
prompt = build_prompt(STANDALONE, top2)
print(prompt, "\n")
reply, reason = ask(llm, prompt)
answer = reply.text.strip() if reply else ""
print(answer or {"answer": None, "reason": reason or "empty reply"})Code walkthrough
build_promptjoins each source with its ID in brackets, then adds the rules and the question.ask(llm, prompt)sends it through the guard.answer or {...}prints a clear "no answer" result when the provider failed or replied with nothing.
Look for "yes", the 5-year motor cover, and the IDs [pol-warranty-2026] and [man-x200-errors]. Wording varies by provider.
Now LangChain. HuggingFaceEmbeddings wraps the same bge model, InMemoryVectorStore stores the vectors, and a prompt template piped into llm makes a chain: a sequence of steps run with one call.
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
lc_embed = HuggingFaceEmbeddings(model_name="BAAI/bge-small-en-v1.5", encode_kwargs={"normalize_embeddings": True})
store = InMemoryVectorStore(lc_embed)
store.add_documents([Document(page_content=d["text"], metadata={"id": d["id"]}) for d in DOCS])
hits = store.as_retriever(search_kwargs={"k": 3}).invoke(STANDALONE)
print("LangChain top 3:", [h.metadata["id"] for h in hits])
template = ChatPromptTemplate.from_template("Answer using only these sources, citing ids in brackets.\n{sources}\n\nQuestion: {question}")
inputs = {"sources": "\n".join(f"[{h.metadata['id']}] {h.page_content}" for h in hits), "question": STANDALONE}
print(template.invoke(inputs).to_messages()[0].content[:160], "...")
reply, reason = ask(template | llm, inputs)
print(reply.text.strip() if reply and reply.text.strip() else {"answer": None, "reason": reason or "empty reply"})Code walkthrough
store.as_retriever(...).invoke(question)embeds the question and returns the 3 nearestDocumentobjects. They print aspol-warranty-2026,man-x200-errors,man-x200-specs: the same as NumPy.template.invoke(inputs)fills the template; printing it shows the exact text the chain will send.template | llmis the chain;asksends it the inputs dictionary.
This chain skips the reranker, so the X200 specification page goes along as a third source; adding a reranker is a step you can now write yourself. LlamaIndex packs the same steps into fewer lines. Its documentation (not run here) shows:
# not run here: LlamaIndex is shown as an example only.
# From https://developers.llamaindex.ai/python/framework/getting_started/starter_example/ (checked 12 October 2026)
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = await query_engine.aquery(query)Code walkthrough
SimpleDirectoryReaderloads a folder,VectorStoreIndexembeds and stores,as_query_engineretrieves and answers. The default model is OpenAI's, per its documentation.
In production
Teams pin versions and keep model names in one place, as this course does. LangChain's versioning page (checked 12 October 2026) says LangChain and LangGraph 1.0 "are designated as LTS releases", that breaking changes come "only in major releases", and that after 2.0, 1.0 enters maintenance "for at least 1 year". What is likely to change: model names on all three providers, which models are free, and integration packages. The langchain-community repository now says it "has been sunset" and was archived on 19 June 2026, so this course uses partner packages such as langchain-huggingface. Check course-guide/02 before assuming a name still works.
What you added to Hollin Answers
- A stack map: one tool per step, with a clear line where text leaves the notebook.
- The setup cell, a key check that never prints a key, and the
ask()guard. - bge-small embeddings, a cross-encoder reranker and a Chroma collection over 14 documents.
- One grounded, cited answer to Ellie, built in plain Python and again in LangChain.
Check yourself
- Which steps in this lesson send Hollin's text to an outside service?
Only the answer step, which calls the language model. Embedding, reranking and the Chroma store all run in the notebook. With Ollama, nothing leaves the machine at all.
- You switch from Groq to Gemini. What do you change?
One word: PROVIDER = "gemini", plus a GOOGLE_API_KEY secret. MODELS already holds the model name and init_chat_model builds the right connector.
- Why did the mismatched query in "Break it" not raise an error?
Both models make 384 numbers, so the shapes match and the dot product runs. The numbers mean different things in each model, so the scores dropped to about 0.2 and the ranking changed.
- The reranker scores a document -2.59. Is it irrelevant?
Not necessarily. MS MARCO cross-encoders return logits, not probabilities. For "can I put the blender base in the sink" the correct page scored -2.59 and still came first.
Exercise
Write retrieve(question, k=5, keep=2): bge-small top k from D, reranked with reranker, returning the IDs of the best keep. Then run the check.
def check(retrieve):
got = retrieve("is blade wear covered for the X200") # q23 in the shared data
assert got[0] == "pol-warranty-2026", got
assert retrieve(STANDALONE) == ["man-x200-errors", "pol-warranty-2026"], retrieve(STANDALONE)
print("passed:", got)
# check(retrieve) # uncomment once the function is writtenCode walkthrough
- The first check is q23: bge-small ranks the blade manual above the warranty, and the reranker should fix it. The second repeats Ellie's question.
Key terms
| Term | Meaning | |
|---|---|---|
| Pinned version | A library installed at an exact version, so code behaves the same later | |
| Environment variable | A named value given to a running program; where keys are read from | |
| Connector | The library that speaks one provider's API behind LangChain's common interface | |
| Guard | A wrapper that retries a model call once, then returns a named reason | |
| Embedding model | A model that turns text into a vector of numbers | |
| Cosine similarity | How closely two vectors point the same way; a dot product for unit vectors | |
| Cross-encoder | A model that reads a question and a document together and scores the pair | |
| Logit | A raw model score with no fixed range; only its order is meaningful here | |
| Vector store | A store of vectors, IDs and metadata that finds the nearest vectors to a query | |
| Chain | LangChain steps joined with ` | ` and run with one call |
Sources
- Max Woolf, "The Problem With LangChain", 2023. https://minimaxir.com/2023/07/langchain-problem/
- LangChain, "Models" and "Versioning", documentation, checked 12 October 2026. https://docs.langchain.com/oss/python/langchain/models and https://docs.langchain.com/oss/python/versioning
- Groq, "Supported models", checked 12 October 2026. https://console.groq.com/docs/models
- OpenAI, "Models", checked 12 October 2026. https://developers.openai.com/api/docs/models
- Anthropic, "Models overview", checked 12 October 2026. https://platform.claude.com/docs/en/about-claude/models/overview
- Google, "Gemini models" and "Embeddings", checked 12 October 2026. https://ai.google.dev/gemini-api/docs/models and https://ai.google.dev/gemini-api/docs/embeddings
- Sentence Transformers, "Cross Encoder usage", checked 12 October 2026. https://sbert.net/docs/cross_encoder/usage/usage.html
- Chroma, "Getting started", checked 12 October 2026. https://docs.trychroma.com/docs/overview/getting-started
- LangChain,
langchain-communityrepository README, archived 19 June 2026. https://github.com/langchain-ai/langchain-community - LlamaIndex, "Starter tutorial", checked 12 October 2026. https://developers.llamaindex.ai/python/framework/getting_started/starter_example/