Strona główna / Artykuły / LangChain 1.x w praktyce: łańcuchy, RAG, narzędzia i agenci lokalnie

LangChain 1.x w praktyce: łańcuchy, RAG, narzędzia i agenci lokalnie

Naucz się tworzyć łańcuchy, generację wzbogaconą o dane z bazy, narzędzia oraz agenty typu RAG za pomocą LangChain 1.x przy użyciu darmowej lokalnej instalacji Ollama – nie są potrzebne klucze API.

3586 słów

To jest trzeci odcinek z serii praktycznych poradników, kontynuujący wcześniejsze prace nad tworzeniem RAG oraz agentów od zera przy użyciu czystego Pythona. Podejście tutaj jest celowo inne: zamiast samodzielnie składać każdą część, zobaczysz, jak LangChain pakuje tę samą strukturę w zaledwie kilka linijek kodu. Ponieważ już samodzielnie stworzyłeś podstawowe elementy, masz doskonałą pozycję, by zrozumieć, co dokładnie robi każda abstrakcja, zamiast traktować ją jak coś magicznego. To zrozumienie jest kluczowe – to właśnie ono odróżnia programistów, którzy efektywnie korzystają z LangChain, od tych, którzy ciągle z nim walczą.

Wszystko w tym poradniku działa lokalnie i bezpłatnie, wykorzystując lokalny model przez Ollamę wraz z lokalnymi embeddingami. Nie ma potrzeby używania kluczy API ani nie trzeba się martwić ograniczeniami szybkości.

Uwaga dotycząca wersji: ten przewodnik jest przeznaczony dla LangChain 1.x, sprawdzony z użyciem wersji langchain==1.3.11 oraz langchain-core==1.4.8. Wersja LangChain 1.0 przyniosła znaczną reorganizację – obecna API agenta opiera się na funkcji create_agent, natomiast starsze komponenty, takie jak AgentExecutor i initialize_agent, zostały przeniesione do odrębnego pakietu langchain-classic. Wiele przewodników dostępnych w Internecie nadal pokazuje wzorce z okresu przed wersją 1.0; tutaj przedstawione importy są aktualne, a każdy z nich został sprawdzony, aby upewnić się, że działa poprawnie.

Jak postępować: otwórz plik o nazwie lc.py, uruchom każdy blok kodu po kolei i wykona ćwiczenia „Twoja kolej”, gdy tylko do nich dojdziesz. Gdy zobaczysz frazę „to ty to stworzyłeś”, odnosi się ona do ręcznej implementacji z wcześniejszych tutoriali w tej serii.

Krok 0 — Czym właściwie jest LangChain

LangChain najlepiej rozumieć jako zbiór standaryzowanych, wymiennych komponentów służących do budowania aplikacji wykorzystujących LLM — takich jak otulacze modeli, szablony promptów, mechanizmy wyszukiwania, magazyny wektorowe, narzędzia i agenci. Wszystkie te elementy spełniają wspólną interfejs, co oznacza, że możesz je połączyć ze sobą i zastępować jedne drugimi (np. użyć innego modelu lub zmienić magazyn wektorowy) bez konieczności przepisywania logiki aplikacji.

Koncepcją, która łączy wszystko razem, jest Runnable. Każdy komponent udostępnia tę samą metodę .invoke(), a dowolne dwa komponenty można połączyć ze sobą za pomocą operatora rury |. Ten mechanizm rurowania nazywa się LCEL, skrótem od LangChain Expression Language. Gdy każda część systemu używa interfejsu Runnable, cały pipeline RAG lub agent może zostać zapisany w zaledwie kilku liniach.

Otwarta kwestia kompromisu, którą warto wspomnieć od razu: LangChain redukuje ilość powtarzalnego kodu i daje dostęp do szerokiego katalogu gotowych integracji. W zamian wprowadza warstwy abstrakcji, które mogą utrudnić debugowanie — będą chwile, gdy wolałbyś patrzeć na prosty pętlę, który sam napisałeś. Umiejętność oceny, kiedy ta abstrakcja jest warta poniesionych kosztów, to prawdziwa umiejętność, a ten kompromis omówimy ponownie w Kroku 7.

Ustawianie (bezpłatna, lokalna środowisko)

pip install langchain langchain-core langchain-ollama langchain-huggingface langchain-text-splitters sentence-transformers

Będziesz również potrzebował zainstalowanego Ollamy (jest darmowy i działa lokalnie); następnie powinieneś pobrać model zdolny do wywoływania narzędzi:

ollama pull llama3.2     # ~2 GB; needs ~8 GB RAM. qwen2.5 also works well.

Jeśli wolisz pominąć Ollamę, nadal możesz uruchomić sekcje chain i RAG przy użyciu lokalnego modelu Hugging Face poprzez langchain-huggingface. Sekcje agent wymagają jednak niezawodnego działania narzędzi, z którymi się komunikuje model, a małe modele ograniczone zasobami CPU zazwyczaj radzą sobie z tym słabo. Zdecydowanie zaleca się używanie Ollamy w krokach od 4 do 6.

Krok 1 — Podstawa: łańcuch z użyciem operatora „|”

W poprzednim tutorialu o RAG ręcznie skompilowałeś prompt za pomocą ciągu znaków f, przekazałeś go modelowi i oczyściłeś wynik za pomocą .strip(). LangChain przechowuje tę samą sekwencję jako wyrażenie z użyciem operatora „|”. Dodaj to do pliku lc.py:

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = ChatOllama(model="llama3.2", temperature=0)

prompt = ChatPromptTemplate.from_template(
    "Explain {topic} in exactly one sentence."
)

# The chain: prompt -> model -> plain-string parser
chain = prompt | llm | StrOutputParser()

print(chain.invoke({"topic": "retrieval-augmented generation"}))

Czytanie łańcucha przepływu od lewej do prawej: prompt przekształca podany słownik na poprawnie sformatowaną wiadomość, llm zamienia tę wiadomość na odpowiedź modelu, a StrOutputParser() wydobywa tekst prosty z obiektu odpowiedzi.

Już to stworzyłeś. Ten łańcuch przepływu jest funkcjonalnie identyczny z f"Explain {topic}..." połączonym z generator(prompt), a następnie z [0]["generated_text"].strip() z wcześniejszego przewodnika po RAG — trzy ręczne kroki, teraz przedstawione jako trzy elementy typu Runnable połączone łańcuchem. Logika się nie zmieniła; jedynie interfejs został ustandaryzowany.

Twoja kolej: każdy element typu Runnable obsługuje również .batch() i .stream() bez dodatkowych ustawień. Spróbuj tego:

for piece in chain.stream({"topic": "vector embeddings"}):
    print(piece, end="", flush=True)   # tokens arrive as they're generated
print()
print(chain.batch([{"topic": "agents"}, {"topic": "chunking"}]))  # two at once

Zauważ, że transmisja strumieniowa i grupowanie danych przyszły „za darmo”, po prostu dlatego, że użyłeś standardowej interfejsu Runnable. Ten moment „za darmo” stanowi w skrócie całe uzasadnienie dla używania LangChain.

Krok 2 — RAG w stylu LangChain

Nadszedł czas, aby odbudować ręcznie tworzony pipeline RAG przy użyciu elementów budulcowych LangChain. Każdy z tych elementów odpowiada bezpośrednio temu, co już napisałeś ręcznie.

from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter

# Same Nimbus knowledge base from the RAG tutorial
DOCUMENTS = [
    "Nimbus is a fictional note-taking app. The free plan, Nimbus Lite, allows up to 50 notes and 1 GB of storage.",
    "Nimbus Pro costs 8 dollars per month billed annually, or 10 dollars billed monthly. It includes 50 GB of storage and collaboration for up to 5 people.",
    "Nimbus stores notes encrypted at rest with AES-256. End-to-end encryption is Pro-only and must be enabled in Settings > Security.",
    "Nimbus offers a 30-day refund policy on all paid plans. Refunds reach the original payment method within 5 business days.",
    "Nimbus live chat support is staffed for Pro customers, Monday to Friday, 9am-6pm UTC. Free users get email support with a 48-hour response time.",
]

# 1. Split (↔ your chunk_text function)
splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=40)
chunks = splitter.create_documents(DOCUMENTS)

# 2. Embed locally (↔ your sentence-transformers model)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")

# 3. Store + index (↔ your numpy array of vectors). No server needed.
vectorstore = InMemoryVectorStore.from_documents(chunks, embeddings)

# 4. Retriever (↔ your retrieve() with cosine top-k)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

for doc in retriever.invoke("How much does Pro cost?"):
    print("-", doc.page_content[:70], "...")

↔ to ty to stworzyłeś — całość.** RecursiveCharacterTextSplitter pełni rolę narzędzia do dzielenia tekstu, ale robi to bardziej starannie: dzieli tekst według granic akapitów i zdań, a nie tylko liczy słowa. HuggingFaceEmbeddings to otoczka dla tego samego modelu all-MiniLM-L6-v2, który używałeś wcześniej. InMemoryVectorStore zastępuje twoją tablicę wektorów typu numpy, a jego metoda .as_retriever() wykonywać takie samo poszukiwanie top-k na podstawie podobieństwa kosinowego, jakie napisałeś ręcznie. Cztery linijki kodu tutaj obejmują wszystko, co stworzyłeś w krokach od 2 do 4 wcześniej.

Następnie połącz funkcję wyszukiwania z generowaniem za pomocą LCEL:

from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

rag_prompt = ChatPromptTemplate.from_template(
    "Answer using only the context. If it's not there, say you don't know.\n\n"
    "Context:\n{context}\n\nQuestion: {question}\nAnswer:"
)

def format_docs(docs):
    return "\n\n".join(d.page_content for d in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

print(rag_chain.invoke("How much does Nimbus Pro cost per month?"))

Słownik na początku łańcucha uruchamia dwa węzły równolegle: question po prostu przekazuje dane wejściowe bez zmian, natomiast context kieruje te same dane przez mechanizm wyszukiwania i formatuje wyniki. Oba wyjścia trafiają następnie do promptu. ↔ to ty to stworzyłeś to w zasadzie twoja stara funkcja rag_answer() – wyszukaj, wstaw do promptu, wygeneruj – skondensowana w jednej instrukcji.

Pora na Ciebie: Spróbuj wywołać rag_chain.invoke("Czy użytkownicy bez konta mogą korzystać z czatu na żywo?"), a następnie dodaj pytanie niezwiązane z nim, na przykład rag_chain.invoke("Jaka jest stolica Francji?"). Uważnie obserwuj odpowiedź „Nie wiem” — to ta sama weryfikacja z wcześniejszego tutorialu RAG, która podkreśla ten sam fakt: jakość pozyskiwania informacji decyduje o jakości odpowiedzi. Następnie wywołaj samodzielnie retriever.invoke(...), aby dokładnie zobaczyć, jakie dane zostały pobraane, gdy odpowiedź wydaje się błędna. To rozdzielenie — weryfikacja pozyskiwania informacji niezależnie od ich generowania — to nawyk debugowania, którego używałeś już wcześniej, a LangChain zachowuje go, traktując te dwa kroki jako odrębne elementy do uruchomienia.

Krok 3 — Narzędzia

W instruktorium dotyczącym agentów zdefiniowałeś narzędzia jako słownik TOOLS i sam napisałeś parser oparty na regex, aby wydobyć nazwę narzędzia oraz jego dane wejściowe z surowego tekstu wyjściowego modelu. LangChain całkowicie eliminuje potrzebę takiego parsera dzięki natywnemu wywoływaniu narzędzi: opisujesz, co robi dane narzędzie, model odpowiada strukturyzowanym wywołaniem, a LangChain zajmuje się kierowaniem. Definicja narzędzia wygląda w ten sposób, przy użyciu dekoratora @tool:

from langchain_core.tools import tool
import ast, operator, datetime

_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
        ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg}
def _ev(n):
    if isinstance(n, ast.Constant): return n.value
    if isinstance(n, ast.BinOp):   return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
    if isinstance(n, ast.UnaryOp): return _OPS[type(n.op)](_ev(n.operand))
    raise ValueError("unsupported")

@tool
def calculator(expression: str) -> str:
    """Evaluate a basic arithmetic expression like '8 * 12'."""
    return str(_ev(ast.parse(expression, mode="eval").body))

@tool
def get_today(_: str = "") -> str:
    """Return today's date in ISO format."""
    return datetime.date.today().isoformat()

Oto szczegóły, nad którymi warto się zatrzymać. Możesz dokładnie sprawdzić, co LangChain wygenerowało na podstawie twojej funkcji:

print(calculator.name)         # 'calculator'
print(calculator.description)  # the docstring
print(calculator.args)         # {'expression': {'title': 'Expression', 'type': 'string'}}

To ostatnie wiersz to autentyczny, zweryfikowany wynik. LangChain przeanalizował twoje wskazówki typowe (expression: str) wraz z dokumentacją i na ich podstawie stworzył schemat — to właśnie ten schemat jest czytany przez model, aby zdecydować, czy i w jaki sposób wywołać narzędzie. ↔ to ty go stworzyłeś, choć wcześniej ręcznie wpisywałeś opisy narzędzi do swojego SYSTEM_PROMPT i sam analizowałeś wyniki modelu. Teraz sama dokumentacja staje się opisem, a analiza odbywa się automatycznie. To wyjaśnia, dlaczego dokumentacje i wskazówki typowe mają rzeczywiste znaczenie — nie są jedynie dokumentacją, lecz określają, w jaki sposób model rozumie i używa narzędzia. Niedbale napisana dokumentacja powoduje, że model wywołuje narzędzie błędnie.

Pora na Ciebie: Zastąp dokumentację kalkulatora czymś nieskutecznym, na przykład """wykonuje obliczenia""", a następnie sprawdź ponownie .description. Na etapie 4 zobaczysz na własne oczy, jak słabsza dokumentacja prowadzi do gorszych decyzji modelu dotyczących wyboru narzędzia. Opis, który napiszesz, pełni rolę kierownicy dla zachowania modelu.

Etap 4 — Agenci w jednej wywołaniu

Tutaj przynoszą owoce wcześniejsze prace. Tworzony ręcznie agent wymagał pętli, tablicy do przechowywania danych, parsera, obsługi błędów, ograniczenia liczby kroków oraz instrukcji systemowych, które uczyły model formatu ReAct. W LangChain 1.x wszystko to sprowadza się do jednego wywołania funkcji: create_agent.

from langchain.agents import create_agent

agent = create_agent(
    model=llm,                                   # your ChatOllama from Step 1
    tools=[calculator, get_today],               # the @tool functions from Step 3
    system_prompt="You are a helpful assistant. Use tools for math and dates.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What is 8 times 12, and what is today's date?"}]}
)
print(result["messages"][-1].content)

To jest cały agent, od początku do końca. ↔ to ty go stworzyłeś — całość. Funkcja create_agent wewnętrznie uruchamia cykl rozumowanie-działanie-obserwacja, kieruje zadania do odpowiednich narzędzi, wprowadza uzyskane obserwacje z powrotem do modelu, sprawdza warunki zatrzymania oraz egzekwuje limit kroków — wszystko to zostało zbudowane ręcznie wewnątrz funkcji run_agent. W tle polega ona na LangGraph, dlatego zachowanie pętli jest tak niezawodne.

Jeśli chcesz obserwować proces rozumowania — taki sam efekt, jaki dawało ci ustawienie verbose=True — przesyłaj kroki pośrednie zamiast czekać tylko na ostateczną odpowiedź:

inputs = {"messages": [{"role": "user", "content": "How much is a year of Nimbus Pro?"}]}
for chunk in agent.stream(inputs, stream_mode="updates"):
    print(chunk)

Pora na ciebie: Spróbuj zadać pytanie, które zmusi model do połączenia dwóch narzędzi — na przykład poproś go o obliczenie daty zakończenia 30-dniowego okna na zwrot pieniędzy, biorąc pod uwagę, że próbna wersja rozpoczęła się dzisiaj. Obserwuj, czy poprawnie wywoła najpierw get_today, a następnie calculator w kolejności. Następnie wróć do kroku 7 materiału o agentach — każdy z opisanych tam sposobów awarii (zmiany formatu wyjścia, wymyślone nazwy narzędzi, pętle nieskończone) może się pojawić również tutaj. Framework nie ulepsza sposobu rozumowania słabego modelu; jedynie ukrywa podstawowe mechanizmy działania. Zrozumienie tej różnicy jest właśnie powodem, dla którego szybciej naprawisz błędy w tych agentach niż osoba, która od razu przeszła do frameworka, nie budując najpierw modelu.

Krok 5 — Połączenie wszystkiego: agent, który pobiera dane (agentic RAG)

Tutaj łączą się wszystkie trzy poprzednie lekcje. Weź swojego psa poszukiwawczego i uformuj go jako narzędzie, a następnie przekaż to narzędzie agencie. Od tego momentu to sam agent decyduje kiedy konieczne jest wyszukiwanie dokumentów — może je przeprowadzać kilka razy lub łączyć proces poszukiwania z obliczeniami według potrzeb.

@tool
def search_nimbus_docs(query: str) -> str:
    """Search the Nimbus product documentation for facts about plans, pricing, refunds, security, and support."""
    docs = retriever.invoke(query)
    return "\n\n".join(d.page_content for d in docs)

smart_agent = create_agent(
    model=llm,
    tools=[search_nimbus_docs, calculator, get_today],
    system_prompt=(
        "You answer questions about the Nimbus app. "
        "Use search_nimbus_docs for any product facts, and calculator for arithmetic. "
        "Base answers only on retrieved facts."
    ),
)

q = "How much would Nimbus Pro cost a team of 4 for a full year?"
result = smart_agent.invoke({"messages": [{"role": "user", "content": q}]})
print(result["messages"][-1].content)

Aby poprawnie odpowiedzieć na takie pytanie, agent musi najpierw wyszukać cenę miesięcznej subskrypcji, a dopiero potem obliczyć wartość 8 * 12 * 4. To jest proces pozyskiwania informacji (z pierwszego tutorialu) przedstawiony jako narzędzie (z trzeciego), sterowany przez agenta (z drugiego) — trzy odrębne koncepcje działające jako jeden system. Pozwalanie agentowi na decydowanie o czasie pozyskiwania informacji jest znacznie bardziej elastyczne niż użycie sztywno zdefiniowanego ścieżki rag_chain z kroku 2, a to wzorzec często spotykany w rzeczywistych systemach produkcyjnych.

Pora na Ciebie: Nadaj również transmisję wykonywania zadań przez tego agenta, używając smart_agent.stream(..., stream_mode="updates"), i potwierdź kolejność działań — wyszukiwanie odbywa się przed obliczeniami. Jeśli Twój lokalny model próbuje rozwiązywać zadania arytmetyczne samodzielnie zamiast korzystać z narzędzia kalkulatora (co jest powszechną tendencją u mniejszych modeli), wzmocnij instrukcje systemowe zdaniem takim jak „MUSISZ używać kalkulatora przy każdym kroku arytmetycznym”. To ta sama metoda, która zadziałała w instrukcjach dotyczących agentów.

Krok 6 — Krótki przegląd pozostałych elementów

W tym momencie masz już podstawową strukturę. Warto poznać kilka dodatkowych elementów budulcowych LangChain, z których każdy odnosi się do czegoś, co już samodzielnie stworzyłeś:

  • Ładowarki dokumentów (langchain-community) — umożliwiają bezpośrednie pobieranie plików PDF, stron internetowych, stron Notion oraz podobnych źródeł do tych samych obiektów Document, które już jest wykorzystywanych przez narzędzie do dzielenia tekstu. Zastępuje to ręczne wklejanie tekstu do listy prawdziwym, zstrukturyzowanym procesem pobierania danych.
  • Magazyny wektorowe klasy produkcyjnej — można zastąpić lokalny magazyn w pamięci Chroma lub FAISS (impordowane za pomocą from langchain_chroma import Chroma) w celu przechowywania embeddingów na dysku i zwiększenia możliwości poza granicami dostępnej pamięci. Ponieważ oba narzędzia oferują tę samą interfejs .as_retriever(), nic w pozostałych elementach łańcucha nie musi ulegać zmianie — właśnie ta spójność stanowi główny cel tej zamiany.
  • Pamięć i historia wiadomości — umożliwia zachowanie kontekstu z poprzednich kroków, przekształcając jednorazową sekwencję zapytań w ciągłą rozmowę.
  • Parserzy wyjściowe poza zwykłymi ciągami znaków — przechodzą odpowiedź modelu do formatu JSON lub zweryfikowanego obiektu Pydantic, zamiast polegać na tym, że surowy tekst będzie automatycznie dobrze sformatowany.
  • LangGraph — gdy logika pętli wbudowana w create_agent okazuje się niewystarczająca (ścieżki rozgałęziające się, kroki zatwierdzania przez człowieka, współpraca kilku agentów), stosuje się LangGraph – silnik grafowy niższego poziomu, na którym opiera się create_agent.
  • Krok 7 — Kiedy używać LangChain, a kiedy nie (szczerze)

    W tym momencie stworzyłeś już dwa razy ten sam typ systemu — raz od zera, a raz przy użyciu frameworka — co stawia cię w dobrej pozycji, by samodzielnie wykonać tę operację. W tym właśnie polega sens przeanalizowania obu wersji.

    LangChain okazuje się przydatny, gdy łączysz wiele istniejących integracji — kilka narzędzi do ładowania dokumentów, kilka magazynów wektorowych, więcej niż jeden dostawca modeli — i nie chcesz ręcznie implementować logiki strumieniowania, grupowania danych, ponawiania prób oraz śledzenia dla każdej z nich. Jest również przydatny, gdy planujesz często zmieniać poszczególne elementy i potrzebujesz stabilnej interfejsu do ich wymiany, albo gdy budujesz agenta i wolisz nie zajmować się osobiście pętlą rozumowania.

    Pisanie ręcznie jest często lepszym rozwiązaniem, gdy aplikacja jest na tyle mała, że nauka abstrakcji LangChain zajęłaby więcej czasu niż samo napisanie pięćdziesięciu linijek kodu, które już potrafimy napisać. Jest to również lepszy wybór, gdy potrzebujemy pełnej widoczności tego, co się wykonuje – przechodzenie przez warstwy frameworka w celu naprawy błędu stanowi prawdziwe źródło trudności, a to zastrzeżenie jest uzasadnione – albo gdy dodanie warstwy pośredniczącej ukryłoby logikę, która w rzeczywistości jest jaśniejsza w prostym kodzie Pythona. Pipeline RAG oraz agent napisany ręcznie w wcześniejszych instrukcjach są w pełni przydatne do użycia w produkcji; używanie frameworka w żaden sposób nie sprawia, że kod napisany ręcznie jest gorszy.

    Nie ma tu jednej poprawnej odpowiedzi. Powodem, dla którego najpierw nauczyłeś się wersji ręcznej, jest to, aby wybór tego frameworka był świadomą decyzją podjętą przy pełnej świadomości tego, co zastępuje, a nie opcją domyślną, do której się ucieka, ponieważ mechanizmy wewnętrzne pozostają tajemnicą.

    Krok 8 — Dalej co robić

    • Jeśli twoje lokalne środowisko wydaje się zbyt wolne, dostępne są bezpłatne wersje modeli hostowanych przez Groq i Google Gemini. Przełączenie się polega na jednej zmianie – zastąp ChatOllama przez ChatGroq lub użyj init_chat_model("gemini-...", model_provider="google_genai") – ponieważ wszystko inne działa za pomocą tej samej standardowej interfejsu. Do obu potrzebny będzie klucz API, ale ich wersje bezpłatne nie kosztują nic.
  • LangSmith to narzędzie do śledzenia i debugowania w LangChain. Gdy łańcuch lub agent zachowuje się nietypowo, umożliwia sprawdzenie każdego kroku, każdego wejścia oraz każdego wyjścia. Dostępna jest wersja darmowa i stanowi bezpośrednią odpowiedź na skargi typu „Nie mogę zobaczyć, co dzieje się wewnątrz frameworka”.
  • LangGraph warto przetestować, gdy potrzebujesz pracujących w trybie stanowym procesów, kilku współpracujących agentów lub punktów kontrolnych z udziałem człowieka, które wykraczają poza prosty cykl wywołań narzędzi.
  • Oficjalna dokumentacja znajduje się pod adresem docs.langchain.com. Upewnij się, że materiały, które czytasz, odnoszą się do wersji 1.x – wszystko napisane przed wersją 1.0 odnosi się do AgentExecutor, initialize_agent lub LLMChain, które zostały już przeniesione lub uznane za przestarzałe.
  • Model myślowy, który warto zapamiętać

    LangChain to w istocie własne, ręcznie stworzone komponenty, ustandaryzowane za pomocą jednej interfejsu — Runnable — i połączone za pomocą |. Nic z tego nie stanowi naprawdę nowej koncepcji, gdy już samodzielnie stworzysz te elementy:

    • Łańcuch to proces od promptu przez model do parsera, który już napisałeś, po prostu połączony ze sobą.
    • Retriever to twoja logika embed-and-cosine-search, opakowana w wspólny interfejs.
    • Narzędzie to funkcja, którą napisałeś, wraz z automatycznie generowanym schematem, umożliwiającym modelowi bezpośrednie wywołanie jej, zamiast abyś sam analizował jego tekstowy wynik.
    • Agent, za pośrednictwem create_agent, to cały twój cykl rozumowanie-działanie-obserwacja, sprowadzony do jednego wywołania.

    Gdy coś idzie nie tak, naprawiasz to w taki sam sposób jak zawsze: izolujesz usterkowy komponent. Testujesz moduł samodzielnie, wyświetlasz wartość .args narzędzia lub przesyłasz dane o pośrednich krokach agenta. Framework zmienia jedynie ilość kodu, którą musisz wpisać — nie zmienia tego, co faktycznie się dzieje, a ty i tak rozumiesz, co się dzieje.

    Rozwiązywanie problemów

    • Jeśli napotkasz błąd ImportError przy wywołaniu funkcji create_agent lub langchain_ollama, prawdopodobnie używasz wersji przed 1.0 lub brakuje ci jakiegoś pakietu. Uruchom polecenie pip install -U langchain langchain-ollama i sprawdź, czy langchain.__version__ zaczyna się od 1..
  • Jeśli tutorial, który czytasz, używa AgentExecutor lub initialize_agent, to jest to starsza API. W wersji 1.x została ona przeniesiona do langchain-classic; nowy kod powinien zamiast tego używać create_agent.
  • Błąd „connection refused” od Ollamy oznacza, że serwer nie jest uruchomiony — uruchom go za pomocą ollama serve lub otwórz aplikację i upewnij się, że twój model pojawił się w wyniku wywołania ollama list.
  • Jeśli agent ignoruje narzędzie lub próbuje samodzielnie wykonywać obliczenia, model prawdopodobnie nie działa poprawnie przy wywoływaniu narzędzi, co jest powszechnym ograniczeniem mniejszych modeli, albo dokumentacja narzędzia jest zbyt niejasna. Ulepsz prompt systemowy oraz dokumentację narzędzia lub spróbuj silniejszego modelu, takiego jak qwen2.5.
  • HuggingFaceEmbeddings wydaje się wolny przy pierwszym użyciu, ponieważ pobiera model embeddingów (około 80 MB) i przechowuje go lokalnie. Sam proces wyszukiwania przebiega szybko po tym wydarzeniu.
  • Pierwsze wezwanie modelu przez Ollamę jest wolne, ponieważ model jest ładowany do pamięci RAM; kolejne wezwania są szybkie.
  • Teraz stworzyłeś RAG, agenty oraz framework, który je obejmuje — najpierw ręcznie, a potem za pomocą LangChain. Rozumiesz warstwę, do której większość ludzi ma dostęp tylko z zewnątrz. Smacznego korzystania.

    Literatura pokrewna

  • Zarządzanie LLM-jako-sędzią jako żywym systemem produkcyjnym — Dowiedz się, jak czterostopniowy cykl życia Netflixa — dane oparte na faktach, szkolenie dostosowane do kryteriów oceny, bezpieczne wdrożenie oraz ciągłe monitorowanie — zapewnia dokładność sędziego LLM w skali masowej.