---
title: "LangChain și LangGraph explicate: setul de instrumente esențial | Runtime lines"
description: "Trusa esențială pentru dezvoltatorii AI care lucrează în Python: runnable-uri și chain-uri LCEL, modele de chat și tool-uri, regăsire (retrieval) și agenți LangGraph cu stare, checkpoint-uri și human-in-the-loop (aprobare umană)."
url: https://superintelligence.ro/ro/langchain
alternate_en: https://superintelligence.ro/langchain.md
alternate_ro: https://superintelligence.ro/ro/langchain.md
---

# LangChain și LangGraph, trusa esențială

LangChain îți dă piese standard pentru aplicații LLM: modele de chat, mesaje, prompturi, tool-uri și retriever-e, care se îmbină toate ca Runnables. LangGraph le rulează ca graf, cu stare, bucle, memorie și aprobare umană. Parcurge pas cu pas patru programe scurte, apoi citește despre piese, tipare și întrebările pe care le pun intervievatorii.

**Fiecare program de pe această pagină rulează cu adevărat, fără cheie API.** Modelul este un model de chat fals din `langchain_core.language_models` care redă răspunsuri scriptate, deci fiecare output afișat este real. În aplicația ta scrii în schimb `model = init_chat_model("provider:model-name")` (cu pachetul furnizorului instalat, de exemplu `langchain-openai` sau `langchain-anthropic`), iar restul codului rămâne neschimbat.

## Piesele de bază

Totul în LangChain implementează o singură interfață, `Runnable`. Îi înveți metodele o dată și poți apela, compune, face streaming și testa fiecare piesă la fel: prompturi, modele, parsere, retriever-e, tool-uri, chain-uri întregi și grafuri compilate.

### Modele de chat și mesaje

`init_chat_model("provider:model-name")` returnează un model de chat pentru orice furnizor suportat, cu același API. Un model primește o listă de mesaje și returnează un `AIMessage`.

- **SystemMessage**: instrucțiuni pentru model.
- **HumanMessage**: ce spune utilizatorul.
- **AIMessage**: răspunsul. Poate conține `tool_calls` și `usage_metadata` (numărul de tokenuri).
- **ToolMessage**: rezultatul unui tool, legat de apelul lui prin `tool_call_id`.

În 1.x, `message.text` este o proprietate care dă textul simplu, iar `message.content_blocks` dă blocuri tipizate (text, raționament, apeluri de tool-uri, imagini) în aceeași formă pentru orice furnizor. Streamingul produce obiecte `AIMessageChunk`, pe care le poți aduna cu `+`.

### Șabloane de prompt

`ChatPromptTemplate` transformă un dict într-o listă de mesaje. `MessagesPlaceholder` inserează o listă întreagă, de obicei istoricul conversației.
from langchain_core.messages import AIMessage, HumanMessage from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages( [ ("system", "You are a {role}."), MessagesPlaceholder("history"), ("human", "{question}"), ] ) value = prompt.invoke( { "role": "tutor", "history": [HumanMessage("Hi"), AIMessage("Hello!")], "question": "What is LCEL?", } ) print([(m.type, m.content) for m in value.to_messages()]) [('system', 'You are a tutor.'), ('human', 'Hi'), ('ai', 'Hello!'), ('human', 'What is LCEL?')]

### Output structurat și parsere

`model.with_structured_output(Schema)` returnează un Runnable care îți dă un obiect validat în loc de un mesaj. În spate folosește modul JSON nativ al furnizorului sau apelarea tool-urilor. Pasează `include_raw=True` ca să primești și mesajul brut, plus eventuala eroare de parsare.
from langchain_core.language_models import GenericFakeChatModel from langchain_core.messages import AIMessage from pydantic import BaseModel class FakeChatModel(GenericFakeChatModel): def bind_tools(self, tools, **kwargs): return self class Movie(BaseModel): """A movie mentioned in the text.""" title: str year: int call = {"name": "Movie", "args": {"title": "Inception", "year": 2010}, "id": "c1"} model = FakeChatModel(messages=iter([AIMessage("", tool_calls=[call])])) extractor = model.with_structured_output(Movie) movie = extractor.invoke("Nolan's 2010 film about dreams") print(repr(movie)) Movie(title='Inception', year=2010)

Parserele de output fac același lucru într-un chain: `StrOutputParser` pentru text, `JsonOutputParser` și `PydanticOutputParser` pentru modelele fără output structurat nativ.

### Tool-uri

`@tool` transformă o funcție cu tipuri într-un tool: numele vine din funcție, descrierea din docstring, iar schema argumentelor din type hints (sau dintr-un `args_schema` Pydantic). Modelul vede doar aceste trei lucruri, așa că scrie-le pentru model.

- `model.bind_tools([...])` îi permite modelului să decidă singur să le apeleze.
- `tool.invoke(tool_call)` returnează un `ToolMessage` cu `tool_call_id` corect.
- `ToolNode` rulează în paralel toate apelurile din ultimul `AIMessage`. Implicit, trimite înapoi modelului erorile de argumente invalide și relansează celelalte excepții; cu `handle_tool_errors=True` returnează orice eroare ca mesaj.

### RAG: documente, splittere, embedding-uri, retriever-e

- **Loaderele** citesc sursele în obiecte `Document` (`page_content` plus `metadata`).
- **Text splitterele** (pachetul `langchain-text-splitters`) le împart în chunk-uri. `RecursiveCharacterTextSplitter` încearcă întâi paragrafe, apoi rânduri, apoi cuvinte; `chunk_size` limitează mărimea fiecărui chunk, iar `chunk_overlap` repetă puțin text între vecini, ca o idee să nu fie tăiată în două.
- **Embedding-urile** transformă textul în vectori (`init_embeddings("provider:model-name")`); un **vector store** îi păstrează și îi găsește pe cei mai apropiați.
- `store.as_retriever()` îți dă un **retriever**: un Runnable care duce de la un șir de interogare la o listă de documente, deci intră direct într-un chain.
from langchain_core.documents import Document from langchain_core.embeddings import DeterministicFakeEmbedding from langchain_core.language_models import FakeListChatModel from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.vectorstores import InMemoryVectorStore docs = [ Document("LangGraph adds state, cycles and checkpoints.", metadata={"src": "a"}), Document("LCEL joins Runnables with the | operator.", metadata={"src": "b"}), ] store = InMemoryVectorStore.from_documents(docs, DeterministicFakeEmbedding(size=64)) retriever = store.as_retriever(search_kwargs={"k": 1}) prompt = ChatPromptTemplate.from_template( "Answer from the context only.\nContext: {context}\nQuestion: {question}" ) model = FakeListChatModel(responses=["(the model's grounded answer)"]) def format_docs(found): return "\n\n".join(d.page_content for d in found) rag = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | model | StrOutputParser() ) print(rag.invoke("What does LangGraph add?"))

Embedding-urile false de aici sunt vectori aleatori, deci clasamentul nu înseamnă nimic; pune în loc un model real de embedding și codul rămâne același.

## Chain, create_agent sau graf personalizat?

Alege cea mai simplă formă care se potrivește. Fiecare formă se construiește pe cea dinainte și toate trei sunt Runnables, deci poți urca mai târziu fără să-ți rescrii tool-urile sau prompturile.

-  Pașii sunt ficși, fără buclă? **Un chain (LCEL)**

Prompt, model, parser, poate un retriever. Previzibil, ieftin și ușor de testat. Majoritatea aplicațiilor de extragere, clasificare, rezumare și RAG sunt chain-uri.
prompt | model | parser

-  Modelul alege tool-uri până termină? **create_agent**

Bucla standard de apelare a tool-urilor, construită pe LangGraph. Adaugi comportament cu **middleware** în loc să rescrii bucla: aprobare umană, rezumarea istoricelor lungi, reîncercări, modele de rezervă, limite de apeluri, mascarea datelor personale (PII).
create_agent(model, tools, middleware=[...])

-  Ai nevoie de propriul flux de control? **Un StateGraph personalizat**

Mai mulți agenți, ramificații, fan-out paralel, aprobare la un anumit pas, joburi de lungă durată care trebuie să supraviețuiască repornirilor. Tu proiectezi starea, nodurile și muchiile.
StateGraph(State).add_node(...)

### create_agent construiește bucla agentului pentru tine

Același model fals și același tool ca în explorator. Rezultatul este un graf LangGraph compilat, cu nodurile `model` și `tools`. `system_prompt` este trimis la fiecare apel al modelului, dar nu e salvat în stare.
from langchain.agents import create_agent from langchain_core.language_models import GenericFakeChatModel from langchain_core.messages import AIMessage, HumanMessage from langchain_core.tools import tool class FakeChatModel(GenericFakeChatModel): def bind_tools(self, tools, **kwargs): return self @tool def get_weather(city: str) -> str: """Get the current weather for a city.""" return f"Sunny, 21 C in {city}" call = {"name": "get_weather", "args": {"city": "Paris"}, "id": "call_1"} model = FakeChatModel( messages=iter([AIMessage("", tool_calls=[call]), AIMessage("Sunny, 21 C.")]) ) agent = create_agent(model, tools=[get_weather], system_prompt="You report weather.") result = agent.invoke({"messages": [HumanMessage("Weather in Paris?")]}) print(list(agent.get_graph().nodes)) print([m.type for m in result["messages"]]) ['__start__', 'model', 'tools', '__end__'] ['human', 'ai', 'tool', 'ai']

### Middleware: hook-uri în jurul buclei

Middleware-ul învelește apelurile de model și de tool-uri din `create_agent`. Cele incluse, toate în `langchain.agents.middleware`:

- `HumanInTheLoopMiddleware`: pune pauză înaintea tool-urilor alese; un om poate aproba, edita, respinge sau răspunde.
- `SummarizationMiddleware`: rezumă mesajele vechi când istoricul devine lung.
- `ModelRetryMiddleware`, `ToolRetryMiddleware`, `ModelFallbackMiddleware`: reîncearcă cu backoff, apoi schimbă modelul.
- `ModelCallLimitMiddleware`, `ToolCallLimitMiddleware`: limitează apelurile per rulare sau per thread.
- `PIIMiddleware`: maschează sau blochează datele personale.

Scrie-ți propriul middleware cu decoratorii `@before_model`, `@after_model`, `@wrap_model_call`, `@wrap_tool_call` și `@dynamic_prompt`.

### Și când nu-ți trebuie niciunul

Un singur apel de model, fără tool-uri, fără regăsire și fără pași? Ajunge SDK-ul furnizorului sau un simplu `init_chat_model(...).invoke(...)`. Framework-urile merită când schimbi furnizori, compui pași, faci streaming, tracing sau rulezi bucle cu stare. Nu adăuga straturi pe care nu le poți explica.

## Concepte de bază în LangGraph

O aplicație LangGraph înseamnă o **stare** (un TypedDict, un dataclass sau un model Pydantic), **noduri** care o citesc și returnează actualizări, și **muchii** care decid ce nod rulează în continuare. Rulează în **super-pași** (super-steps): toate nodurile programate pentru un pas rulează (în paralel, dacă sunt mai multe), actualizările lor se combină, apoi începe pasul următor.

### Stare și reducer-e

Nodurile returnează **actualizări parțiale**, niciodată toată starea. O cheie fără reducer e suprascrisă; o cheie adnotată cu un reducer e combinată. `add_messages` adaugă mesajele noi la final și îl înlocuiește pe cel cu același `id`; așa editezi un mesaj.
import operator from typing import Annotated, TypedDict from langchain_core.messages import AIMessage, HumanMessage from langgraph.graph import add_messages class State(TypedDict): question: str # no reducer: a new value overwrites the old one log: Annotated[list[str], operator.add] # reducer: lists are concatenated messages: Annotated[list, add_messages] # append, or replace by message id print(operator.add(["start"], ["agent"])) old = [HumanMessage("Hi", id="1"), AIMessage("Draft", id="2")] print([m.content for m in add_messages(old, [AIMessage("Final", id="2")])]) print([m.content for m in add_messages(old, [AIMessage("More")])]) ['start', 'agent'] ['Hi', 'Final'] ['Hi', 'Draft', 'More']

`MessagesState` este starea gata făcută, doar cu `messages` și `add_messages`; moștenește din ea ca să adaugi chei.

### Noduri, muchii și muchii condiționate

- Un **nod** este o funcție (sincronă sau asincronă) care primește starea și returnează un dict de actualizări.
- `add_edge(a, b)` merge mereu de la `a` la `b`. `START` și `END` marchează intrarea și ieșirea.
- `add_conditional_edges(a, router)` apelează `router(state)` și merge unde indică valoarea returnată. Dă o listă sau un dict de destinații, ca graful să poată fi desenat.
- Un nod poate returna și `Command(goto="b", update={...})`, ca să actualizeze starea și să aleagă ruta în același loc.
- O muchie înapoi spre un nod anterior formează un **ciclu**. Bucla asta e diferența dintre un graf și un chain.

### Cicluri și limita de recursivitate

Fiecare super-pas se numără la `recursion_limit`. Când o rulare atinge limita, LangGraph aruncă `GraphRecursionError` în loc să ruleze la nesfârșit. O setezi per apel, în config.
from typing import TypedDict from langgraph.errors import GraphRecursionError from langgraph.graph import START, StateGraph class State(TypedDict): n: int builder = StateGraph(State) builder.add_node("loop", lambda s: {"n": s["n"] + 1}) builder.add_edge(START, "loop") builder.add_edge("loop", "loop") # a cycle with no exit graph = builder.compile() try: graph.invoke({"n": 0}, {"recursion_limit": 5}) except GraphRecursionError as e: print(type(e).__name__, str(e).splitlines()[0]) GraphRecursionError Recursion limit of 5 reached without hitting a stop condition. You can increase the limit by setting the `recursion_limit` config key.

Versiunile mai vechi aveau implicit 25 de pași. În langgraph 1.2 valoarea implicită este 10.007 (`LANGGRAPH_DEFAULT_RECURSION_LIMIT`), iar `create_agent` setează 9.999, așa că pune-ți propria limită pentru agenții care pot intra în buclă.

### Checkpointere, thread-uri și time travel

Compilezi cu un checkpointer și fiecare super-pas este salvat ca **checkpoint** sub `thread_id` din config. Asta îți dă memorie de conversație, rulări reluabile după un crash, interrupt-uri și time travel: te întorci la un checkpoint vechi, îl modifici și rulezi mai departe pe o ramură nouă.
from typing import TypedDict from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import START, StateGraph class State(TypedDict): n: int builder = StateGraph(State) builder.add_node("double", lambda s: {"n": s["n"] * 2}) builder.add_node("inc", lambda s: {"n": s["n"] + 1}) builder.add_edge(START, "double") builder.add_edge("double", "inc") graph = builder.compile(checkpointer=InMemorySaver()) config = {"configurable": {"thread_id": "t"}} print(graph.invoke({"n": 5}, config)) history = list(graph.get_state_history(config)) # newest first before_inc = next(c for c in history if c.next == ("inc",)) print(before_inc.values) fork = graph.update_state(before_inc.config, {"n": 100}) print(graph.invoke(None, fork)) {'n': 11} {'n': 10} {'n': 101}

Folosește `InMemorySaver` în teste și un saver pe bază de date în producție, de exemplu `PostgresSaver` din `langgraph-checkpoint-postgres`.

### Interrupt-uri

- `interrupt(value)` într-un nod pune rularea pe pauză și returnează `value` apelantului, sub `__interrupt__`. Are nevoie de un checkpointer.
- Reiei cu `graph.invoke(Command(resume=answer), config)` pe același thread. Nodul **repornește de la prima linie**, iar `interrupt()` returnează `answer`.
- Din cauza repornirii, pune efectele secundare după apelul `interrupt()` sau fă-le idempotente.
- `interrupt_before` și `interrupt_after`, setate la compilare, pun pauză în jurul unor noduri întregi; sunt utile mai ales la depanare.

### Send: map-reduce și ramuri paralele

Când numărul de ramuri se știe abia la rulare, returnează dintr-o muchie condiționată o listă de `Send(node, input)`. Fiecare rulează nodul cu propriul input, în paralel, în același super-pas, iar un reducer colectează rezultatele.
import operator from typing import Annotated, TypedDict from langgraph.graph import START, StateGraph from langgraph.types import Send class State(TypedDict): topics: list[str] summaries: Annotated[list[str], operator.add] def fan_out(state: State): return [Send("summarise", {"topic": t}) for t in state["topics"]] def summarise(item: dict): return {"summaries": [f"summary of {item['topic']}"]} builder = StateGraph(State) builder.add_node("summarise", summarise) builder.add_conditional_edges(START, fan_out, ["summarise"]) graph = builder.compile() print(graph.invoke({"topics": ["cats", "dogs", "owls"]})) {'topics': ['cats', 'dogs', 'owls'], 'summaries': ['summary of cats', 'summary of dogs', 'summary of owls']}

Pentru un set fix de ramuri nu ai nevoie de `Send`: adaugi mai multe muchii care pleacă din același nod, iar destinațiile rulează în paralel.

### Subgrafuri

Un graf compilat este un Runnable, deci poate fi nod în alt graf. Dacă au chei de stare comune, îl dai direct lui `add_node`. Dacă schemele diferă, îl apelezi dintr-o funcție-nod și mapezi starea la intrare și la ieșire. Cu subgrafuri construiești sisteme multi-agent din agenți mai mici, testabili.
from typing import TypedDict from langgraph.graph import START, StateGraph class State(TypedDict): text: str def clean(state: State): return {"text": state["text"].strip()} inner = StateGraph(State) inner.add_node("clean", clean) inner.add_edge(START, "clean") cleaner = inner.compile() outer = StateGraph(State) outer.add_node("cleaner", cleaner) # a compiled graph is a node outer.add_node("shout", lambda s: {"text": s["text"].upper()}) outer.add_edge(START, "cleaner") outer.add_edge("cleaner", "shout") print(outer.compile().invoke({"text": " hi "})) {'text': 'HI'}

### Memorie pe termen lung: Store

Un checkpointer ține minte un singur **thread**. Un **Store** păstrează documente JSON în namespace-uri, partajate între thread-uri: preferințele utilizatorului, fapte învățate. Nodurile ajung la el, ca și la `context`-ul rulării, prin argumentul `Runtime`.
from dataclasses import dataclass from langgraph.graph import START, MessagesState, StateGraph from langgraph.runtime import Runtime from langgraph.store.memory import InMemoryStore @dataclass class Context: user_id: str def remember(state: MessagesState, runtime: Runtime[Context]): ns = ("users", runtime.context.user_id) runtime.store.put(ns, "prefs", {"language": "Romanian"}) return {} builder = StateGraph(MessagesState, context_schema=Context) builder.add_node("remember", remember) builder.add_edge(START, "remember") store = InMemoryStore() graph = builder.compile(store=store) graph.invoke({"messages": []}, context=Context(user_id="ana")) print(store.get(("users", "ana"), "prefs").value) {'language': 'Romanian'}

Cu un index de embedding-uri configurat, `store.search(namespace, query=...)` găsește amintiri după sens.

## Esențiale pentru producție

Părțile despre care întreabă intervievatorii după ce demo-ul tău merge: să vezi ce s-a întâmplat, să reziști la furnizori instabili, să oprești buclele scăpate de sub control și să testezi fără să plătești tokenuri.

### Tracing cu LangSmith

Setezi `LANGSMITH_TRACING=true` și `LANGSMITH_API_KEY` (și, opțional, `LANGSMITH_PROJECT`), iar fiecare rulare de chain, agent sau graf e înregistrată ca arbore: input-urile, output-urile, latența, consumul de tokenuri și erorile fiecărui pas. Fără modificări de cod. Ca să incluzi și funcțiile tale, învelește-le cu `@traceable` din pachetul `langsmith`.

### Evaluare cu LangSmith

Păstrezi un **dataset** cu input-uri de exemplu și output-uri de referință, rulezi aplicația pe el cu `evaluate(target, data=..., evaluators=[...])` și notezi fiecare rezultat cu verificări în cod sau cu un LLM judecător. Fiecare rulare este un **experiment**, deci poți compara prompturi și modele unul lângă altul înainte de lansare, apoi rulezi evaluatori online pe trace-urile din producție.

### Reîncercări, fallback-uri și limite de rată

`with_retry` reîncearcă un Runnable cu backoff exponențial; `with_fallbacks` încearcă următorul Runnable dacă tot eșuează. Aici modelul principal dă mereu timeout:
from langchain_core.language_models import FakeListChatModel from langchain_core.runnables import RunnableLambda attempts = [] def flaky_model(prompt): attempts.append(prompt) raise TimeoutError("provider timed out") primary = RunnableLambda(flaky_model) backup = FakeListChatModel(responses=["Answer from the backup model"]) model = primary.with_retry(stop_after_attempt=3).with_fallbacks([backup]) print(model.invoke("hi").content) print(len(attempts)) Answer from the backup model 3

Ca să rămâi sub limita de rată a furnizorului, dă-i modelului de chat `rate_limiter=InMemoryRateLimiter(requests_per_second=...)`. În agenți, folosește middleware-ul de retry și de fallback.

### Limite și conversații lungi

- Setează `recursion_limit`, iar în agenți `ModelCallLimitMiddleware` sau `ToolCallLimitMiddleware`, ca un model derutat să nu intre în buclă și să cheltuiască la nesfârșit.
- Istoricul ajunge să depășească fereastra de context: scurtează-l cu `trim_messages` sau rezumă-l cu `SummarizationMiddleware`.
- Folosește un checkpointer pe bază de date și alege `durability` (`"sync"`, `"async"` sau `"exit"`) ca să schimbi siguranță pe viteză.

### Teste cu modele false

`FakeListChatModel` redă stringuri, iar `GenericFakeChatModel` și `FakeMessagesListChatModel` redau `AIMessage`-uri întregi, inclusiv `tool_calls`. Scrii dinainte ce răspunde modelul, rulezi chain-ul sau graful real și verifici mesajele, traseul parcurs și starea finală, exact ca în fiecare program de pe pagina asta. Testele unitare rămân rapide, gratuite și deterministe; calitatea răspunsurilor o verifici separat, cu evaluările din LangSmith.

### LangChain, LangGraph, LangSmith

- **LangChain**: piesele (modele, mesaje, tool-uri, retrievere), LCEL și `create_agent`.
- **LangGraph**: runtime-ul pentru fluxuri cu stare, cu bucle și durabile. `create_agent` rulează pe el.
- **LangSmith**: tracing, evaluare și monitorizare. Merge cu sau fără celelalte două.

## De reținut

**Totul e un Runnable**`invoke`, `batch`, `stream` și perechile lor async merg pe orice piesă. `|` construiește un `RunnableSequence`; un dict devine un `RunnableParallel`.
**Mesajele sunt interfața**Mesaje System, Human, AI și Tool. Un `AIMessage` poate cere tool-uri; fiecare `ToolMessage` răspunde unui singur apel, prin `tool_call_id`.
**Modelul nu rulează niciodată tool-uri**Returnează `tool_calls`. Codul tău, `ToolNode` sau `create_agent` le rulează și trimite rezultatele înapoi.
**Un agent e o buclă**Model, tool-uri, model, până nu mai există apeluri de tool-uri. `create_agent` o construiește; cu `StateGraph` îi dai tu forma.
**Stare plus reducer-e**Nodurile returnează actualizări parțiale, iar reducer-ele le combină. `add_messages` adaugă la final sau înlocuiește după id.
**Checkpointer plus thread_id**Perechea asta îți dă memorie, interrupturi cu `Command(resume=...)`, recuperare după crash și time travel. Un Store ține minte între threaduri.

## Întrebări de interviu: LangChain și LangGraph

Răspunsuri scurte pe care le poți spune cu voce tare. Încearcă să răspunzi singur la fiecare întrebare înainte s-o deschizi.

### La ce folosește LangChain și când nu l-ai folosi?

LangChain îți dă piese standard, independente de furnizor, pentru aplicații cu LLM: modele de chat, mesaje, prompturi, tool-uri, retrievere și parsarea outputului, toate compozabile ca Runnables, plus `create_agent` pentru agenți cu tool calling. Merită când compui pași, schimbi furnizori, faci streaming, tracing sau construiești agenți. Pentru un singur apel de model fără tool-uri sau regăsire, SDK-ul furnizorului e mai simplu și n-ar trebui să adaugi un strat pe care nu-l poți explica.

### Care e diferența dintre LangChain, LangGraph și LangSmith?

LangChain e setul de blocuri de construcție plus `create_agent`, de nivel înalt. LangGraph e runtime-ul de nivel jos pentru fluxuri cu stare: grafuri cu stare, cicluri, checkpointuri, interrupturi și streaming; `create_agent` rulează pe el. LangSmith e platforma de observabilitate și evaluare și merge cu sau fără celelalte două.

### Ce e un Runnable și ce e LCEL?

Un Runnable e orice are interfața standard: `invoke`, `batch`, `stream` și versiunile lor async. Prompturile, modelele, parserele, retrieverele, tool-urile și grafurile compilate sunt toate Runnables. LCEL, LangChain Expression Language, le compune cu `|`, care construiește un `RunnableSequence` în care outputul fiecărui pas e inputul pasului următor. Chain-ul compus e și el un Runnable, așa că primește gratis streaming, batching, async și tracing.

### Care e diferența dintre `invoke`, `batch`, `stream` și `astream`?

`invoke` primește un input și returnează un output. `batch` primește o listă și rulează inputurile concurent pe un thread pool, limitat de `max_concurrency`. `stream` emite outputul pe bucăți, de exemplu tokenuri, imediat ce sunt gata. `astream` și celelalte metode cu `a` sunt versiunile async pentru servere asyncio, ca un apel lent de model să nu blocheze alte requesturi.

### Ce fac `RunnableParallel`, `RunnablePassthrough` și `RunnableLambda`?

`RunnableParallel` rulează mai multe Runnables pe același input și returnează un dict cu rezultatele lor; un dict simplu într-un chain e transformat automat într-unul. `RunnablePassthrough` lasă inputul să treacă neschimbat, iar `RunnablePassthrough.assign` adaugă chei calculate într-un dict de input. `RunnableLambda` împachetează o funcție Python obișnuită ca să poată sta într-un chain. Chain-ul RAG clasic folosește toate trei ideile: `{"context": retriever | format, "question": RunnablePassthrough()} | prompt | model`.

### Care sunt tipurile de mesaje și ce înseamnă fiecare?

`SystemMessage` poartă instrucțiunile, `HumanMessage` inputul utilizatorului, iar `AIMessage` răspunsul modelului, care poate include `tool_calls` și `usage_metadata`. `ToolMessage` poartă rezultatul unui tool și trebuie să aibă `tool_call_id` al apelului căruia îi răspunde. Un model de chat primește o listă de mesaje și returnează un `AIMessage`; în 1.x, `.text` îți dă textul, iar `.content_blocks` îți dă blocuri tipizate, cu aceeași formă pentru orice furnizor.

### Cum funcționează tool calling, pas cu pas?

Legi tool-urile cu `model.bind_tools(tools)`, care trimite odată cu requestul numele, descrierea și schema JSON a fiecărui tool. Modelul nu rulează nimic: returnează un `AIMessage` cu `tool_calls`, fiecare cu nume, argumente și un id. Codul tău rulează fiecare tool și adaugă un `ToolMessage` cu `tool_call_id` corespunzător, apoi apelează din nou modelul cu tot istoricul. Când modelul răspunde fără apeluri de tool-uri, ai terminat.

### `bind_tools` sau `with_structured_output`: când îl folosești pe fiecare?

Folosește `bind_tools` când modelul trebuie să decidă dacă acționează și ce tool-uri apelează, ca într-un agent. Folosește `with_structured_output(Schema)` când vrei mereu date într-o formă fixă, de exemplu la extragere sau clasificare: impune formatul prin modul JSON al furnizorului sau printr-un apel de tool, apoi parsează rezultatul în modelul tău Pydantic, TypedDict sau dict. `include_raw=True` returnează în plus mesajul brut și eventuala eroare de parsare.

### Cum funcționează bucla unui agent?

Modelul primește conversația și schemele tool-urilor. Dacă returnează apeluri de tool-uri, tool-urile rulează și rezultatele lor se adaugă ca `ToolMessage`-uri, apoi modelul e apelat din nou. Bucla se termină când modelul răspunde fără apeluri de tool-uri sau când o limită o oprește. În LangGraph asta înseamnă două noduri, model și tools, o muchie condiționată precum `tools_condition` și o muchie de la tools înapoi la model.

### Când ai folosi `create_agent` și când un graf LangGraph construit de mână?

`create_agent(model, tools, ...)` îți dă bucla standard de tool calling peste LangGraph, cu checkpointing, streaming și middleware pentru lucruri ca aprobarea umană, rezumarea, reîncercările și limitele de apeluri. Folosește-l ori de câte ori forma potrivită e un model care alege tool-uri într-o buclă. Construiește-ți propriul `StateGraph` când ai nevoie de un flux de control personalizat: pași ficși amestecați cu pași de agent, mai mulți agenți, fan-out paralel sau aprobare la un anumit pas.

### Ce e middleware-ul în `create_agent`?

Hookuri care rulează în jurul apelurilor de model și de tool-uri ale agentului, ca să schimbi comportamentul fără să rescrii bucla. Printre cele incluse sunt `HumanInTheLoopMiddleware`, `SummarizationMiddleware`, `ModelRetryMiddleware`, `ModelFallbackMiddleware`, `ModelCallLimitMiddleware`, `ToolCallLimitMiddleware` și `PIIMiddleware`. Pe ale tale le scrii cu decoratori precum `@before_model`, `@after_model`, `@wrap_model_call` și `@dynamic_prompt`.

### Ce sunt `StateGraph`, starea și reducer-ele? De ce `add_messages`?

Un `StateGraph` se construiește dintr-o schemă de stare, de obicei un TypedDict. Nodurile returnează actualizări parțiale, iar **reducer**-ul fiecărei chei decide cum se combină o actualizare: fără reducer, valoarea se suprascrie; cu `operator.add`, se concatenează. `add_messages` adaugă mesajele noi la final și le înlocuiește pe cele cu același id, așa că nodurile pot returna doar mesajul nou, iar nodurile paralele nu se suprascriu unul pe altul. `MessagesState` e starea gata făcută, cu o cheie `messages` care îl folosește.

### Cum funcționează muchiile condiționate și ciclurile?

`add_conditional_edges(node, router)` apelează `router(state)` după nod și merge la nodul returnat sau la `END`. O muchie înapoi spre un nod anterior creează un ciclu, și așa intră agenții în buclă. Un nod poate returna și `Command(goto=..., update=...)` ca să ruteze și să actualizeze starea într-un singur pas. Orice buclă are nevoie de o condiție de ieșire, iar limita de recursivitate e plasa de siguranță.

### Ce e limita de recursivitate și ce se întâmplă când o atingi?

Plafonează numărul de super-pași dintr-o rulare. Când e atinsă, LangGraph aruncă `GraphRecursionError` în loc să ruleze la nesfârșit. O setezi per apel cu `config={"recursion_limit": n}`. Versiunile mai vechi aveau implicit 25; langgraph 1.2 are implicit 10.007, iar `create_agent` folosește 9.999, așa că pentru agenți setează-ți propria limită sau adaugă middleware de limitare a apelurilor.

### Ce face un checkpointer și ce e un thread?

Un checkpointer salvează starea grafului după fiecare super-pas. Un thread e o conversație sau un job, identificat prin `thread_id` în `config["configurable"]`; toate checkpointurile lui sunt stocate sub acel id. Dacă apelezi din nou cu același thread, graful continuă din starea salvată, și de aici ai memoria conversației, recuperarea după crash, interrupturile și time travel. Folosește `InMemorySaver` în teste și un saver Postgres sau SQLite în producție.

### Ce e time travel în LangGraph?

Pentru că fiecare pas are un checkpoint, poți lista stările trecute cu `get_state_history(config)`. Dacă apelezi cu configul unui checkpoint mai vechi, execuția se reia din acel punct. `update_state` pe un checkpoint vechi creează un fork pe care îl poți rula mai departe cu alte valori. Se folosește la debugging, ca să reîncerci de la un pas greșit și ca să-i lași pe utilizatori să editeze un mesaj anterior.

### Cum funcționează `interrupt` și `Command(resume=...)` și unde e capcana?

Un apel `interrupt(value)` într-un nod salvează starea și oprește rularea; apelantul primește `value` sub `__interrupt__`. Ca să continui, apelezi același thread cu `Command(resume=answer)`. Capcana: nodul repornește de la prima linie, iar `interrupt()` returnează atunci `answer`, deci orice cod dinaintea lui rulează de două ori. Pune efectele secundare după interrupt sau fă-le idempotente. Interrupturile au nevoie de un checkpointer.

### Memorie pe termen scurt sau lung: checkpointer sau Store?

Memoria pe termen scurt e starea unui singur thread, păstrată de checkpointer: mesajele conversației curente. Memoria pe termen lung trebuie să supraviețuiască threadurilor, de exemplu preferințele utilizatorului sau fapte aflate mai devreme, așa că merge într-un **Store**: documente JSON sub namespace-uri ca `("users", user_id)`, opțional căutabile după sens. Nodurile ajung la store prin argumentul `Runtime`.

### La ce folosește `Send` și cum funcționează ramurile paralele?

Nodurile programate în același super-pas rulează în paralel, deci mai multe muchii care pleacă din același nod îți dau deja ramuri paralele fixe. Când numărul de ramuri se știe abia la rulare, o muchie condiționată returnează o listă de `Send(node, input)`, iar fiecare rulează nodul cu propriul input: pasul map din map-reduce. Un reducer precum `operator.add` pe cheia de rezultat le colectează outputurile.

### Ce sunt subgrafurile și de ce le folosești?

Un graf compilat e un Runnable, deci poate fi nod în alt graf. Dacă părintele și copilul au chei de stare comune, îl adaugi direct; dacă nu, îl apelezi dintr-un nod și mapezi starea la intrare și la ieșire. Cu subgrafuri construiești și testezi agenții separat și apoi îi combini, iar așa se construiesc de obicei sistemele multi-agent. Un nod dintr-un subgraf poate ruta în părinte cu `Command(graph=Command.PARENT, goto=...)`.

### Ce moduri de streaming are LangGraph?

`values` trimite starea completă după fiecare pas, iar `updates` doar ce a returnat fiecare nod. `messages` trimite tokenurile LLM-ului cu metadate care spun ce nod le-a produs, iar `custom` trimite orice emit nodurile cu `get_stream_writer()`. Mai există `checkpoints`, `tasks` și `debug`; dai o listă ca să primești mai multe, ca tupluri `(mode, data)`.

### Cum faci streaming la răspunsul unui agent într-un UI web?

Folosește `astream` cu `stream_mode="messages"` ca să primești tokenurile pe măsură ce sunt generate, de multe ori combinat cu `"updates"` ca să arăți progresul, de exemplu ce tool rulează. Trimite chunk-urile spre browser prin server-sent events sau printr-un WebSocket. Streamingul de tokenuri merge chiar dacă nodul apelează `invoke`, pentru că LangGraph ascultă callbackurile modelului.

### Cum construiești RAG cu LangChain?

Indexarea: încarci sursele în `Document`-e, le împarți în chunk-uri cu un text splitter, calculezi embedding-urile chunk-urilor și le stochezi într-un vector store. Răspunsul: un retriever găsește chunk-urile cele mai similare cu întrebarea, iar un chain le pune în prompt și îi cere modelului să răspundă doar din acel context. Pentru întrebări mai grele faci din regăsire un tool, ca un agent să poată căuta de mai multe ori și să-și reformuleze interogarea.

### Cum alegi dimensiunea chunk-urilor și suprapunerea (overlap)?

Chunk-urile trebuie să fie destul de mari cât să cuprindă o idee completă și destul de mici încât un chunk regăsit să fie în mare parte relevant; un punct de plecare obișnuit e între câteva sute și vreo mie de tokenuri. Suprapunerea (overlap), de multe ori 10–20%, repetă text între chunk-uri vecine, ca o idee tăiată la graniță să fie găsită totuși. `RecursiveCharacterTextSplitter` împarte după paragrafe, apoi după linii, apoi după cuvinte, ca chunk-urile să rămână naturale. Apoi măsori calitatea regăsirii pe întrebări reale și ajustezi.

### Ce e un retriever și cum diferă de un vector store?

Un vector store stochează embedding-uri și face căutare după similaritate. Un retriever e o interfață Runnable: intră un string de interogare, iese o listă de `Document`-e. `vector_store.as_retriever(search_kwargs={"k": 4})` împachetează un store, dar un retriever poate fi și căutare după cuvinte cheie, o căutare pe web sau un hibrid din mai multe. Fiind un Runnable, se potrivește în orice chain.

### La ce folosește LangSmith?

Tracing: cu `LANGSMITH_TRACING=true` și o cheie API, fiecare rulare e înregistrată ca un arbore de pași, cu inputuri, outputuri, latență, tokenuri și erori, așa că vezi exact ce i s-a trimis modelului. Evaluare: ții dataseturi de exemple, rulezi aplicația pe ele cu evaluatori scriși în cod sau LLM-judge și compari experimentele înainte de lansare. Mai monitorizează și traficul din producție și poate rula evaluări online pe el.

### Cum gestionezi reîncercările, fallback-urile și limitele de rată?

`runnable.with_retry(stop_after_attempt=3)` reîncearcă cu backoff exponențial, iar `with_fallbacks([backup])` încearcă alt model sau alt chain dacă tot eșuează. Dă-i unui model de chat `rate_limiter=InMemoryRateLimiter(requests_per_second=...)` ca să rămâi sub limitele furnizorului. În agenți, `ModelRetryMiddleware`, `ToolRetryMiddleware` și `ModelFallbackMiddleware` fac aceleași lucruri.

### Cum testezi cod LangChain și LangGraph fără să apelezi un model real?

Folosește modelele de chat false din `langchain_core`: `FakeListChatModel` redă stringuri, iar `GenericFakeChatModel` sau `FakeMessagesListChatModel` redau `AIMessage`-uri întregi, inclusiv apeluri de tool-uri. Rulezi chain-ul sau graful real cu un `InMemorySaver` și verifici mesajele, traseul parcurs și starea finală. Așa testele unitare rămân rapide și deterministe; calitatea răspunsurilor o judeci separat, cu evaluările din LangSmith.

### Ce faci cu o conversație care nu mai încape în fereastra de context?

O scurtezi cu `trim_messages`, păstrând mesajul de sistem și mesajele cele mai recente; rezumi turele mai vechi într-un mesaj scurt, ce face `SummarizationMiddleware` pentru agenți; sau muți faptele durabile într-un Store și le regăsești când ai nevoie. Când scurtezi, nu despărți niciodată un `AIMessage` cu apeluri de tool-uri de `ToolMessage`-urile lui, altfel furnizorul respinge istoricul.

### Ce se întâmplă când un tool aruncă o excepție?

Într-un graf, `ToolNode` transformă implicit erorile de argumente invalide într-un `ToolMessage`, ca modelul să-și poată corecta apelul, și aruncă mai departe celelalte excepții. Setează `handle_tool_errors=True` sau dă-i o funcție, ca să trimiți orice eroare înapoi la model ca mesaj. În `create_agent`, middleware-ul de retry poate reîncerca întâi tool-urile instabile.

### Ce face `init_chat_model`?

Creează un model de chat dintr-un string precum `"provider:model-name"`, încărcând pachetul de integrare potrivit, de exemplu `langchain-openai` sau `langchain-anthropic`. Apoi fiecare furnizor are aceeași interfață, așa că schimbi modelele din configurare, fără să modifici codul. Și `create_agent` acceptă direct același string.

### Cum funcționează sistemele multi-agent în LangGraph?

Fiecare agent e un nod sau un subgraf cu propriul prompt și propriile tool-uri. Într-un design cu supervisor, un agent împarte munca celorlalți, de multe ori apelându-i ca tool-uri; într-un design cu handoff, agenții își predau controlul direct cu `Command(goto=...)`. Starea comună sau mesajele duc contextul de la unul la altul. Începe cu un singur agent și adaugă alții doar când tool-urile sau instrucțiunile se separă clar.

Fiecare output de pe pagina asta a fost afișat de programele din `verify/langchain/`, rulate cu langchain 1.4.3, langchain-core 1.6.7 și langgraph 1.2.14 pe Python 3.14. Modelele sunt modelele de chat false din `langchain_core.language_models`; id-urile mesajelor sunt omise din desene.
