LangChain, framework do budowy aplikacji LLM w Pythonie i TypeScripcie
LangChain spina model językowy z resztą aplikacji: promptami, bazą wektorową, narzędziami i historią rozmowy. Wersja 1.0 weszła do produkcji 22 października 2025 roku i sprowadziła rdzeń biblioteki do jednej funkcji budującej agenta oraz zestawu warstw middleware wokół niej. Reszta, czyli setki integracji zebranych przez trzy lata, wylądowała w osobnych pakietach.
Co LangChain robi, a czego nie robi
Podstawowa wartość frameworka to wspólny interfejs nad dostawcami modeli. Zamiast pisać osobny kod dla OpenAI, Claude i modelu uruchomionego lokalnie przez Ollamę, wywołujesz .invoke() na obiekcie, który zachowuje się tak samo niezależnie od tego, co siedzi pod spodem. Tool calling, structured output, streaming i liczenie tokenów mają jedną sygnaturę.
Wspólny interfejs ma granice, o których lepiej wiedzieć wcześniej. Framework ujednolica kształt wywołania, ale nie zasypuje różnic między dostawcami: limity zapytań, cache promptów, parametry sterujące rozumowaniem i format błędów zostają takie, jakie są. Podmiana jednej linijki z nazwą modelu przenosi kod na innego dostawcę, natomiast jakość odpowiedzi i koszt zmieniają się na tyle, że prompt zwykle wymaga dostrojenia. Traktuj tę zamianę jako punkt wyjścia do testów, nie jako gotowy efekt.
LangChain nie jest bazą danych, serwerem ani platformą hostingową. Nie przechowuje wektorów, tylko rozmawia z Chromą, Qdrantem czy Postgresem. Nie uruchamia procesów w tle, od tego jest LangGraph albo zwykła kolejka zadań. Nie zapewnia też jakości odpowiedzi, bo tę mierzy się osobno, narzędziami do ewaluacji. Nie obsługuje również rozmowy głosowej: wykrycie końca wypowiedzi, przerwanie w połowie zdania i telefonia to osobna warstwa, którą bierze na siebie platforma pokroju Vapi. Licząc jej koszt, pamiętaj, że opłata samej platformy to ułamek rachunku, bo do każdej minuty rozmowy dochodzi rozpoznawanie mowy, wywołanie modelu i synteza głosu.
Projekt wystartował w październiku 2022 roku i przez pierwsze dwa lata zbierał zarzuty o nadmiar abstrakcji. Odpowiedzią był podział na pakiety: langchain-core z samymi interfejsami, langchain z klockami do budowy agentów, langchain-classic ze starym kodem trzymanym dla zgodności wstecznej i osobne paczki integracyjne w rodzaju langchain-openai. Deklaracja przy wydaniu 1.0 brzmi: żadnych zmian łamiących API do wersji 2.0. Aktualna wersja langchain-core z końca lipca 2026 to 1.5.3.
Instalacja i pierwszy agent
W Pythonie wystarczą dwa pakiety:
pip install langchain langchain-openai
export OPENAI_API_KEY=sk-...W TypeScripcie analogicznie:
npm install langchain @langchain/openai @langchain/coreMinimalny agent w wersji 1.0 mieści się w kilku linijkach:
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def kurs_waluty(symbol: str) -> str:
"""Zwraca kurs waluty wobec zlotego."""
kursy = {"EUR": "4.28", "USD": "3.94"}
return kursy.get(symbol.upper(), "brak danych")
agent = create_agent(
model="openai:gpt-5",
tools=[kurs_waluty],
system_prompt="Odpowiadasz krotko, po polsku, zawsze podajesz zrodlo liczby."
)
odpowiedz = agent.invoke({"messages": [{"role": "user", "content": "Ile kosztuje euro?"}]})
print(odpowiedz["messages"][-1].content)Pod spodem dzieje się pętla: model dostaje opis narzędzi wygenerowany z sygnatury funkcji i docstringa, decyduje o wywołaniu, framework je wykonuje, wynik wraca do modelu jako wiadomość typu tool. Pętla kończy się, gdy model przestaje prosić o narzędzia. To jest cała magia i dobrze jest ją rozumieć, bo od liczby obrotów zależy rachunek za tokeny.
LCEL, czyli składanie łańcuchów operatorem pipe
Dla zadań, które nie potrzebują agenta, zostaje LangChain Expression Language. Składasz komponenty pionową kreską, a wynik dostaje trzy metody wykonania za darmo.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template(
"Streszcz ponizszy opis bledu w jednym zdaniu:\n\n{tekst}"
)
lancuch = prompt | ChatOpenAI(model="gpt-5-mini", temperature=0) | StrOutputParser()
lancuch.invoke({"tekst": raport})
lancuch.batch([{"tekst": r} for r in raporty])
for fragment in lancuch.stream({"tekst": raport}):
print(fragment, end="")batch sam zrównolegla wywołania, stream oddaje tokeny w miarę ich generowania, a wersje asynchroniczne (ainvoke, astream) działają bez dopisywania kodu. Jeśli piszesz backend, który obsługuje kilkuset użytkowników naraz, ta jedna właściwość oszczędza sporo pracy przy obsłudze współbieżności.
Structured output opiera się na schemacie, najczęściej Pydantic w Pythonie albo Zod w TypeScripcie:
import { z } from 'zod'
import { ChatOpenAI } from '@langchain/openai'
const schema = z.object({
priorytet: z.enum(['niski', 'sredni', 'wysoki']),
kategoria: z.string(),
czasNaprawy: z.number().describe('szacunek w godzinach')
})
const model = new ChatOpenAI({ model: 'gpt-5-mini' }).withStructuredOutput(schema)
const wynik = await model.invoke(`Sklasyfikuj zgloszenie: ${zgloszenie}`)Odpowiedź wraca jako obiekt zgodny ze schematem, bez ręcznego parsowania JSON-a i bez łapania wyjątków po nieudanym JSON.parse.
RAG od dokumentu do odpowiedzi
Retrieval augmented generation to nadal najczęstszy powód sięgania po ten framework. Pipeline ma cztery kroki: wczytanie dokumentów, podział na fragmenty, policzenie embeddingów i zapis do bazy wektorowej.
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
dokumenty = PyPDFLoader("regulamin.pdf").load()
fragmenty = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=120
).split_documents(dokumenty)
baza = Chroma.from_documents(fragmenty, OpenAIEmbeddings(model="text-embedding-3-small"))
retriever = baza.as_retriever(search_kwargs={"k": 4})Dobór rozmiaru fragmentu decyduje o jakości bardziej niż wybór modelu. Fragmenty po 300 znaków gubią kontekst zdania, a po 4000 znaków rozmywają sens i podbijają koszt promptu. Punkt wyjścia dla dokumentacji technicznej to 600 do 1000 znaków z zakładką 10 do 15 procent. Dla umów i regulaminów lepiej dzielić po paragrafach, bo zdanie z jednego paragrafu rzadko wyjaśnia treść innego.
Drugi próg jakości to sam retrieval. Wyszukiwanie po samych wektorach gubi zapytania z numerami wersji i nazwami własnymi, więc w produkcji łączy się je z wyszukiwaniem pełnotekstowym, a wynik przepuszcza przez reranker. Supabase i Neon obsługują oba tryby na jednym Postgresie, co bywa prostsze niż utrzymywanie osobnej bazy wektorowej.
Middleware, czyli kontrola nad pętlą agenta
Wersja 1.0 wprowadziła warstwy, które wpinają się przed wywołaniem modelu i po nim. To odpowiedź na najczęstszy problem produkcyjny: agent działa poprawnie na demie, a po tygodniu rozmowy historia wiadomości przekracza okno kontekstu.
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware, HumanInTheLoopMiddleware
agent = create_agent(
model="anthropic:claude-sonnet-4-5",
tools=[wyszukaj_zamowienie, zwroc_pieniadze],
middleware=[
SummarizationMiddleware(model="openai:gpt-5-mini", max_tokens_before_summary=4000),
HumanInTheLoopMiddleware(interrupt_on={"zwroc_pieniadze": True})
]
)Pierwsza warstwa streszcza starsze wiadomości, gdy historia rośnie. Druga zatrzymuje wykonanie przed narzędziem, które rusza cudze pieniądze, i czeka na akceptację człowieka. Bez tego drugiego mechanizmu każda aplikacja z operacjami zapisu jest zakładem o to, że model nigdy się nie pomyli.
Pamięć rozmowy i stan między sesjami
Pamięć krótkoterminowa to po prostu lista wiadomości przekazywana przy każdym wywołaniu. Trwałość daje checkpointer, który zapisuje stan konwersacji pod identyfikatorem wątku i pozwala wrócić do niej po restarcie procesu.
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string(os.environ["DATABASE_URL"]) as saver:
agent = create_agent(model="openai:gpt-5", tools=narzedzia, checkpointer=saver)
agent.invoke(wejscie, config={"configurable": {"thread_id": "uzytkownik-42"}})Pamięć długoterminowa, czyli fakty o użytkowniku przenoszone między wątkami, to osobny problem. LangChain daje pod nią interfejs store, ale sam nie decyduje, co zapamiętać. Tę decyzję trzeba zaprojektować, najczęściej jako osobne wywołanie modelu, które po rozmowie wyciąga fakty warte zapisania.
Debugowanie, gdy odpowiedź jest zła
Zła odpowiedź ma zwykle jedną z trzech przyczyn i opłaca się je rozdzielić, zanim zaczniesz poprawiać prompt.
Pierwsza: retrieval podał modelowi nie te fragmenty. Sprawdzisz to w dwie minuty, wywołując sam retriever i drukując, co wróciło.
for i, fragment in enumerate(retriever.invoke("jak zlozyc reklamacje")):
print(i, fragment.metadata.get("source"), fragment.page_content[:160])Jeśli wśród czterech fragmentów nie ma tego jednego z odpowiedzią, żaden prompt tego nie naprawi. Wracasz do podziału dokumentów, zwiększasz parametr k albo dokładasz wyszukiwanie pełnotekstowe.
Druga: model dostał właściwy kontekst, ale go zignorował albo dopowiedział szczegół. Pomaga instrukcja, żeby odpowiadał wyłącznie na podstawie podanych fragmentów i wprost przyznawał się do braku danych, oraz temperatura ustawiona na zero przy zadaniach faktograficznych.
Trzecia: agent sięgnął po złe narzędzie. Winny jest zwykle opis narzędzia, nie model. Docstring to jedyne, co model widzi, więc "Zwraca dane" niczego nie rozstrzyga, a "Zwraca status zamówienia po numerze w formacie ORD-12345, tylko dla zamówień z ostatnich 90 dni" wystarcza do trafnego wyboru.
Do podglądu całej pętli włącz śledzenie wywołań.
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__...Każde wywołanie ląduje wtedy w LangSmith jako drzewo z czasami, liczbą tokenów i pełną treścią promptów. Wariant bez zakładania konta to set_debug(True) z langchain_core.globals, który wypisuje te same informacje na standardowe wyjście, tylko trudniej się je czyta.
Osobno pilnuj kosztu już na etapie testów. Callback zliczający tokeny podczas przebiegu zestawu ewaluacyjnego pokazuje, ile realnie kosztuje jedna rozmowa, zanim policzy to faktura.
from langchain_community.callbacks import get_openai_callback
with get_openai_callback() as licznik:
agent.invoke(wejscie)
print(licznik.total_tokens, licznik.total_cost)Zasada, która oszczędza najwięcej czasu, brzmi: testuj warstwy osobno. Najpierw retriever bez modelu, potem prompt bez narzędzi, całość na końcu.
LangChain kontra alternatywy
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| LangChain | Największy zbiór integracji, agenci, jeden interfejs nad providerami | Warstwa abstrakcji do rozgryzienia przy debugowaniu | Aplikacja łączy kilka modeli, narzędzi i źródeł danych |
| LlamaIndex | Indeksowanie i retrieval, gotowe strategie dzielenia dokumentów | Słabsze wsparcie dla złożonych agentów | Rdzeniem produktu jest wyszukiwanie po dokumentach |
| Haystack | Czytelne pipeline'y, dojrzała ewaluacja | Mniejsza społeczność, mniej integracji | Zespół z doświadczeniem w klasycznym NLP |
| SDK dostawcy | Zero abstrakcji, pełna kontrola, najmniej zależności | Każda zmiana dostawcy to przepisanie kodu | Jedno wywołanie modelu w całej aplikacji |
Reguła praktyczna: jeśli aplikacja robi jedno wywołanie modelu i zwraca tekst, framework jest zbędny. Zależność zaczyna się zwracać przy trzecim narzędziu, drugim dostawcy albo pierwszej bazie wektorowej.
Ile to kosztuje
Sam framework jest na licencji MIT i nie kosztuje nic. Płaci się za tokeny oraz opcjonalnie za LangSmith, czyli warstwę obserwowalności tego samego producenta.
| Plan LangSmith | Cena | Limit śladów | Retencja |
|---|---|---|---|
| Developer | 0 USD, jedno stanowisko | 5 000 miesięcznie | 14 dni, rozszerzona do 400 |
| Plus | 39 USD za stanowisko | 10 000 w cenie, potem 2,50 USD za 1 000 | 14 dni, wersja rozszerzona 5 USD za 1 000 |
| Enterprise | wycena indywidualna | negocjowany | 14 dni, rozszerzona do 400 |
Realny rachunek zdominują tokeny, nie subskrypcja. Agent z pięcioma narzędziami i historią rozmowy potrafi zużyć 15 tysięcy tokenów wejściowych na jedno pytanie użytkownika, bo cała historia leci do modelu przy każdym obrocie pętli. Dwie rzeczy tną ten koszt najmocniej: streszczanie historii oraz kierowanie prostych zapytań do mniejszego modelu.
Kolumna retencji wymaga jednego zastrzeżenia, bo tabela sama z siebie sugeruje coś innego. Czternaście dni to podstawa na każdym planie, łącznie z darmowym, a przedłużenie do czterystu dni jest osobno płatną opcją dostępną wszędzie, nie przywilejem wyceny indywidualnej. Jeśli potrzebujesz śladów starszych niż dwa tygodnie do analizy incydentu albo do wymogów zgodności, policz tę pozycję od razu, bo przy dużym ruchu potrafi przewyższyć samą subskrypcję.
Typowe błędy i kiedy odpuścić
Najczęstszy błąd to sięganie po agenta tam, gdzie wystarczy jeden prompt z narzuconym schematem odpowiedzi. Agent kosztuje kilka razy więcej, jest wolniejszy i trudniej go przetestować.
Drugi to brak limitu iteracji. Model, który wpadnie w pętlę wywołań narzędzia, potrafi w kilkanaście minut spalić dzienny budżet. Ustaw twardy limit obrotów i timeout na całe wywołanie.
Trzeci to instalowanie langchain-community bez przypięcia wersji. Ten pakiet zbiera integracje o bardzo różnej jakości, część z nich jest utrzymywana przez pojedyncze osoby i potrafi się zepsuć między wydaniami.
Czwarty dotyczy migracji. Kod pisany pod wersję 0.3 nie zadziała na 1.0 bez zmian, ale nie trzeba go przepisywać od razu: pip install langchain-classic przywraca stare importy i pozwala migrować moduł po module.
Piąty jest najdroższy w skutkach: wdrożenie bez zestawu testowych pytań. Zmiana promptu, modelu albo rozmiaru fragmentu potrafi poprawić dziesięć odpowiedzi i zepsuć trzy inne, a bez listy porównawczej nikt tego nie zauważy do pierwszej skargi użytkownika. Wystarczy trzydzieści pytań z oczekiwaną odpowiedzią, trzymanych w pliku JSON i przepuszczanych przez łańcuch po każdej zmianie. To pół dnia pracy, które zwraca się przy pierwszym wdrożeniu wycofanym w ciągu godziny zamiast po tygodniu.
LangChain w projekcie Next.js
W Next.js framework wpina się w route handler i strumieniuje odpowiedź do przeglądarki. Pełne typowanie po stronie serwera zostaje, bo schematy Zod opisują zarówno wejście, jak i wyjście modelu.
// app/api/chat/route.ts
import { ChatAnthropic } from '@langchain/anthropic'
export const runtime = 'nodejs'
export async function POST(req: Request) {
const { pytanie } = await req.json()
const model = new ChatAnthropic({ model: 'claude-sonnet-4-5' })
const strumien = await model.stream(pytanie)
return new Response(
new ReadableStream({
async start(controller) {
for await (const fragment of strumien) {
controller.enqueue(new TextEncoder().encode(String(fragment.content)))
}
controller.close()
}
}),
{ headers: { 'Content-Type': 'text/plain; charset=utf-8' } }
)
}Na Vercelu pamiętaj o dwóch rzeczach. Runtime edge nie obsługuje części zależności natywnych, więc trasy z bazą wektorową trzymaj na nodejs. Limit czasu funkcji na darmowym planie potrafi uciąć dłuższą rozmowę agenta w połowie, więc długie zadania przenieś do kolejki i odpytuj o wynik.
FAQ
Czy LangChain jest darmowy?
Tak, biblioteka jest open source na licencji MIT i nie ma limitu użycia komercyjnego. Płatne są dopiero usługi wokół niej: LangSmith do śledzenia wywołań oraz hosting agentów. Koszt tokenów naliczy dostawca modelu, niezależnie od tego, czy używasz frameworka.
Python czy TypeScript?
Wersja pythonowa wychodzi pierwsza i ma więcej integracji, zwłaszcza w obszarze przetwarzania dokumentów. Wersja TypeScript nadąża za rdzeniem i wystarcza do agentów, RAG-u i structured output, a wygrywa wtedy, gdy backend i frontend dzielą typy w jednym repozytorium.
LangChain czy LangGraph?
LangChain daje gotowego agenta w jednej funkcji, LangGraph daje graf, w którym sam definiujesz węzły i przejścia. Zacznij od create_agent. Po LangGraph sięgnij, gdy potrzebujesz rozgałęzień, kilku współpracujących agentów albo procesu zatrzymywanego na godziny w oczekiwaniu na akceptację.
Czy framework spowalnia aplikację?
Narzut samego frameworka liczy się w milisekundach i ginie przy czasie odpowiedzi modelu, który wynosi setki milisekund lub sekundy. Realnym źródłem opóźnień jest liczba obrotów pętli agenta oraz sekwencyjne wywołania, które dałoby się zrównoleglić przez batch.
Jak zmigrować z wersji 0.3 na 1.0?
Zainstaluj langchain-classic, podmień importy w jednym module, uruchom testy, przejdź do kolejnego. Najwięcej zmian dotyka agentów, bo AgentExecutor zastąpiła funkcja create_agent. Łańcuchy zbudowane na LCEL przechodzą zwykle bez poprawek.
Dokumentacja frameworka stoi pod adresem docs.langchain.com, a kod źródłowy w repozytorium na GitHubie.