langchain-core reference
Pinned version: langchain-core==1.6.2 (released 2026-09-04). Everything below was derived by
inspecting this exact installed package, and every example was executed against it on Python 3.13.
Each heading links to reference.langchain.com,
which is unversioned and always serves the latest 1.x. Links open in a new tab.
Part of a five-page set — see
how the LangChain stack fits together
for how langchain-core,
langgraph and langchain fit together.
What langchain-core is
langchain-core is the package that defines the data types the rest of the ecosystem passes
around, and the base classes the other packages subclass. Concretely, it ships:
| Module | What it defines |
|---|---|
langchain_core.messages | HumanMessage, AIMessage, ToolMessage, SystemMessage, the tool_calls dict shape, content_blocks |
langchain_core.language_models | BaseChatModel (plus fakes such as GenericFakeChatModel for tests) |
langchain_core.tools | BaseTool, StructuredTool, the @tool decorator, ToolException |
langchain_core.runnables | Runnable, RunnableLambda, RunnableSequence (what | builds), RunnableConfig |
langchain_core.prompts | ChatPromptTemplate, MessagesPlaceholder, PromptTemplate |
langchain_core.output_parsers | StrOutputParser, JsonOutputParser, PydanticOutputParser |
langchain_core.callbacks, .tracers | The callback/tracing hooks LangSmith plugs into |
What it does not ship: an HTTP client for any model provider (that is langchain-anthropic,
langchain-openai, …), an agent loop (that is langchain), a graph runtime or checkpointer (that
is langgraph). A LangGraph agent can accept a chat model from any provider because every provider's
class subclasses the BaseChatModel defined here, and every node exchanges the message classes
defined here.
Layer order — the dependency arrow points one way:
1langchain ──depends on──▶ langgraph ──depends on──▶ langchain-core
Verified from package metadata: langchain 1.4.0 requires langgraph<1.3.0,>=1.2.11 and
langchain-core<2.0.0,>=1.6.0; langgraph 1.2.11 requires langchain-core<2,>=1.4.7 and does not
require langchain at all; langchain-core 1.6.2 requires neither (its own dependencies are
pydantic, langsmith, langchain-protocol, httpx, tenacity, jsonpatch, pyyaml,
typing-extensions, packaging, uuid-utils). Importing every module of langchain_core leaves
langgraph and langchain absent from sys.modules.
Index
| Name | What it defines / does | Most-used methods & fields |
|---|---|---|
BaseChatModel | Abstract class: a subclass implements _generate and _llm_type; in return it inherits invoke/stream/batch, tool binding and structured output | invoke, stream, bind_tools, with_structured_output |
HumanMessage | Message with type="human"; content is a string or a list of content blocks; .text flattens either | content, text, content_blocks |
AIMessage | Message with type="ai" plus tool_calls (what the model wants run), invalid_tool_calls and usage_metadata | text, tool_calls, usage_metadata |
ToolMessage | Message with type="tool"; tool_call_id pairs it with the AIMessage.tool_calls entry it answers; status is "success" or "error" | content, tool_call_id, status, artifact |
SystemMessage | Message with type="system"; providers send it as the system prompt, outside the user/assistant turns | content, text |
@tool | Builds a StructuredTool from a function: name from the function name, description from the docstring, argument schema from the type hints | invoke, name, description, args |
BaseTool | Abstract Runnable: a subclass implements _run; it gets invoke (dict or tool-call in, ToolMessage out), schema generation and error handling | invoke, args, tool_call_schema, handle_tool_error |
Runnable | The calling convention shared by models, tools, prompts and parsers: invoke/batch/stream, async twins, | composition, retries and fallbacks | invoke, batch, stream, pipe (|), with_retry |
ChatPromptTemplate | Holds (role, template) pairs; format_messages(**vars) fills {placeholders} and returns message objects; MessagesPlaceholder splices a history list in | from_messages, format_messages, partial, invoke |
StrOutputParser | Runnable that takes a message (or str) and returns its .text, so a pipeline ends in a plain string | invoke, parse |
BaseChatModel
The abstract base class every chat model subclasses. It leaves exactly two members abstract:
_generate(messages, stop, run_manager, **kwargs) -> ChatResult (one model call) and the _llm_type
property (a string naming the provider). Everything else is inherited:
- Input normalisation.
invokeaccepts astr(wrapped in aHumanMessage), a list of messages, a list of(role, text)tuples or dicts, or aPromptValue, and hands_generatea list ofBaseMessageobjects. It returns oneAIMessage. Runnablemethods.invoke,batch,streamand the asyncainvoke/abatch/astream. If the subclass does not override_stream,streamfalls back to calling_generateonce and yielding a single chunk.bind_tools(tools, tool_choice=None)— returns a newRunnablewith tool schemas attached to each request, so the model can answer withtool_calls. The base implementation raisesNotImplementedError; every provider package overrides it.with_structured_output(schema, include_raw=False)— returns aRunnablewhose output is a dict or Pydantic object matchingschemainstead of anAIMessage. Also provider-implemented.- Bookkeeping fields:
cache,rate_limiter,callbacks,tags,metadata,disable_streaming, andget_num_tokens_from_messages(a rough default; providers override it).
You never instantiate this class directly — a provider package gives you a concrete subclass
(ChatAnthropic, ChatOpenAI, …). The example below implements the two abstract members to show
that this is all a chat model must provide.
1from langchain_core.language_models import BaseChatModel
2from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
3from langchain_core.outputs import ChatGeneration, ChatResult
4
5
6class EchoModel(BaseChatModel):
7 """The two members BaseChatModel leaves abstract — nothing else is required."""
8
9 @property
10 def _llm_type(self) -> str:
11 return "echo"
12
13 def _generate(self, messages, stop=None, run_manager=None, **kwargs) -> ChatResult:
14 reply = AIMessage(f"You said: {messages[-1].text}")
15 return ChatResult(generations=[ChatGeneration(message=reply)])
16
17
18model = EchoModel()
19
20reply = model.invoke([SystemMessage("Be terse."), HumanMessage("Capital of France?")])
21print(type(reply).__name__, "|", reply.text) # AIMessage | You said: Capital of France?
22
23print(model.invoke("hi").text) # You said: hi <- a str becomes a HumanMessage
24print([r.text for r in model.batch(["a", "b"])]) # ['You said: a', 'You said: b']
25print([c.text for c in model.stream("hi")]) # ['You said: hi'] <- no _stream, so one chunk
26
27try:
28 model.bind_tools([])
29except NotImplementedError:
30 print("bind_tools: not implemented here — providers override it")
langchain_core.language_models.GenericFakeChatModel is core's own scripted test double (you hand
it an iterator of replies); the other two references use it so their examples run offline.
HumanMessage
The user's turn. Fields it shares with every message (BaseMessage): content — a str, or a
list of content-block dicts for multimodal input ({"type": "text", ...}, {"type": "image", ...});
id — optional, used by LangGraph's add_messages to replace a message instead of appending;
name; additional_kwargs and response_metadata (provider-specific extras). Its type is fixed
to "human", which is what a provider integration reads to map it onto its user role.
Two properties normalise the shape of content: .text returns the concatenated text however
content is stored, and .content_blocks returns it as a list of typed blocks even when it was a
plain string.
1from langchain_core.messages import HumanMessage
2
3msg = HumanMessage("What is 2 + 2?")
4print(msg.type, "|", msg.text) # human | What is 2 + 2?
5print(msg.content_blocks) # [{'type': 'text', 'text': 'What is 2 + 2?'}]
6
7multimodal = HumanMessage(content=[{"type": "text", "text": "Describe this"},
8 {"type": "image", "url": "https://example.com/cat.png"}])
9print(multimodal.text) # Describe this <- .text skips the non-text block
AIMessage
The model's turn, and the return type of BaseChatModel.invoke. On top of the shared fields it
adds the three that make agent loops possible:
tool_calls— a list of dicts{"name", "args", "id", "type": "tool_call"}, one per tool the model wants executed.argsis already parsed into a Python dict. When a model calls tools,contentis usually empty and this list is the real payload.invalid_tool_calls— calls whose arguments the provider returned but core could not parse as JSON; each carries the raw string and anerror.usage_metadata—{"input_tokens", "output_tokens", "total_tokens"}plus optionalinput_token_details/output_token_details(cache reads, reasoning tokens).
An agent loop reads tool_calls, runs each one, and answers every id with a ToolMessage.
1from langchain_core.messages import AIMessage
2
3msg = AIMessage(
4 content="",
5 tool_calls=[{"name": "add", "args": {"a": 2, "b": 2}, "id": "call_1"}],
6 usage_metadata={"input_tokens": 12, "output_tokens": 7, "total_tokens": 19},
7)
8print(msg.type, "|", msg.tool_calls[0]["name"], msg.tool_calls[0]["args"])
9# ai | add {'a': 2, 'b': 2}
10print(msg.tool_calls[0]["type"], "|", msg.usage_metadata["total_tokens"])
11# tool_call | 19
ToolMessage
The result of running one tool call, appended to the conversation so the model can continue. What it adds to the shared fields:
tool_call_id(required) — must equal theidof theAIMessage.tool_callsentry it answers; providers reject a conversation where a call has no matching result.status—"success"(default) or"error".BaseTool.invokesets"error"when the tool raised andhandle_tool_erroris on; the model sees the error text ascontent.artifact— anything you want to keep for the program but not send to the model (a DataFrame, raw bytes, a file handle). Populated when a tool usesresponse_format="content_and_artifact".
1from langchain_core.messages import ToolMessage
2
3ok = ToolMessage(content="4", tool_call_id="call_1")
4print(ok.type, "|", ok.tool_call_id, "|", ok.status) # tool | call_1 | success
5
6failed = ToolMessage(content="division by zero", tool_call_id="call_2", status="error")
7print(failed.status, "|", failed.text) # error | division by zero
8
9with_artifact = ToolMessage(content="3 rows", tool_call_id="call_3", artifact=[1, 2, 3])
10print(with_artifact.text, "|", with_artifact.artifact) # 3 rows | [1, 2, 3]
SystemMessage
Standing instructions — persona, rules, output format. Its type is "system". Provider
integrations pull it out of the message list and send it through the provider's system-prompt
channel (Anthropic's top-level system parameter, OpenAI's system/developer role) rather than
as a conversational turn, which is why it conventionally sits first and appears once.
langchain.agents.create_agent(system_prompt=...) accepts either a string or a SystemMessage.
1from langchain_core.messages import HumanMessage, SystemMessage
2
3conversation = [SystemMessage("You are a terse calculator."), HumanMessage("2 + 2?")]
4print([m.type for m in conversation]) # ['system', 'human']
@tool
A decorator that builds a StructuredTool (a BaseTool subclass) from a plain function. What it
reads from the function, and what the model then sees when deciding whether to call it:
- name — the function name, unless you pass one:
@tool("add_numbers"). - description — the docstring (
description=overrides). It is sent to the model with every request, so it is part of the prompt. - argument schema — generated from the type hints and defaults.
parse_docstring=Truealso reads a Google-styleArgs:section into per-argument descriptions.args_schema=replaces the inference with a Pydantic model of your own.
Other switches: return_direct=True tells an agent loop to return the tool's output to the user
without another model call; response_format="content_and_artifact" lets the function return a
(content, artifact) pair, where only content reaches the model.
1from langchain_core.tools import tool
2
3
4@tool(parse_docstring=True)
5def add(a: int, b: int) -> int:
6 """Add two integers.
7
8 Args:
9 a: First addend.
10 b: Second addend.
11 """
12 return a + b
13
14
15print(type(add).__name__, "|", add.name, "|", add.description)
16# StructuredTool | add | Add two integers.
17print(add.args)
18# {'a': {'description': 'First addend.', 'title': 'A', 'type': 'integer'},
19# 'b': {'description': 'Second addend.', 'title': 'B', 'type': 'integer'}}
20print(add.invoke({"a": 2, "b": 3})) # 5
BaseTool
The abstract class behind every tool, including the ones @tool builds. A subclass declares
name and description as fields and implements one method, _run(**args); _arun is optional
(the default runs _run in a thread). In return it gets:
- Two ways to be invoked.
invoke({"a": 1})runs the tool and returns its raw result.invoke(tool_call_dict)— the dict straight fromAIMessage.tool_calls— runs it and returns a ready-madeToolMessagecarrying the matchingtool_call_id. Agent loops use the second form. - Schema.
args(the JSON-schema properties) andtool_call_schema(a Pydantic model), derived from_run's signature or fromargs_schema;bind_toolssends this to the model. - Error handling. With
handle_tool_error=True, aToolExceptionraised inside_runbecomes aToolMessagewithstatus="error"instead of propagating; a string or callable there customises the error text.handle_validation_errordoes the same for bad arguments.
Subclass it directly when a tool needs state or setup (a connection, a cache) that a bare function
cannot hold; otherwise @tool is shorter.
1from langchain_core.tools import BaseTool, ToolException
2
3
4class Echo(BaseTool):
5 name: str = "echo"
6 description: str = "Echo the input text."
7
8 def _run(self, text: str) -> str:
9 return text
10
11
12class Boom(BaseTool):
13 name: str = "boom"
14 description: str = "Always fails."
15 handle_tool_error: bool = True
16
17 def _run(self, text: str) -> str:
18 raise ToolException("no can do")
19
20
21print(Echo().invoke({"text": "hello"})) # hello
22print(Echo().tool_call_schema.model_json_schema()["properties"]) # {'text': {'title': 'Text', 'type': 'string'}}
23
24call = {"name": "echo", "args": {"text": "hi"}, "id": "c1", "type": "tool_call"}
25print(repr(Echo().invoke(call)))
26# ToolMessage(content='hi', name='echo', tool_call_id='c1')
27
28call = {"name": "boom", "args": {"text": "x"}, "id": "c2", "type": "tool_call"}
29result = Boom().invoke(call)
30print(result.status, "|", result.content) # error | no can do
Runnable
The one calling convention in core. Chat models, tools, prompts, parsers — and LangGraph's compiled graphs — all subclass it, which is why they are called the same way and can be chained. What it defines:
- Execution:
invoke(input, config=None),batch(inputs),stream(input)(a generator), and the async twinsainvoke,abatch,astream. A subclass implementsinvokeand the rest derive from it unless overridden (batchruns in a thread pool;streamyields one item). - Composition:
a | bbuilds aRunnableSequencethat feedsa's output tob;pipe()is the method form. A plain function or a dict of runnables on either side of|is coerced (RunnableLambda,RunnableParallel). - Behaviour wrappers, each returning a new
Runnable:with_retry(stop_after_attempt=…),with_fallbacks([other]),with_config(tags=…, callbacks=…),bind(**kwargs). - Introspection:
get_input_schema(),get_output_schema(),get_graph()(the same objectCompiledStateGraph.get_graph()returns),astream_events()for a token-by-token event stream.
1import asyncio
2
3from langchain_core.runnables import RunnableLambda
4
5shout = RunnableLambda(lambda s: s.upper())
6exclaim = RunnableLambda(lambda s: s + "!")
7chain = shout | exclaim
8
9print(type(chain).__name__) # RunnableSequence
10print(chain.invoke("hello")) # HELLO!
11print(chain.batch(["a", "b"])) # ['A!', 'B!']
12print(list(chain.stream("hey"))) # ['HEY!']
13print(asyncio.run(chain.ainvoke("async"))) # ASYNC!
14
15safe = (shout | RunnableLambda(lambda s: 1 / 0)).with_fallbacks([exclaim])
16print(safe.invoke("fallback")) # fallback! <- the failing chain fell back
ChatPromptTemplate
A list of message templates that becomes a list of messages once variables are supplied.
from_messages takes (role, template) tuples — roles "system", "human", "ai" — where
{name} placeholders are the variables; input_variables lists what it found. Filling it:
format_messages(**vars)returns message objects;invoke({"k": v})does the same through theRunnableinterface, which is how a template becomes the first stage ofprompt | model.MessagesPlaceholder("history")is a slot that splices an entire list of messages in — the standard way to insert prior conversation turns.partial(**vars)pre-fills some variables and returns a template needing the rest.
1from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
2
3prompt = ChatPromptTemplate.from_messages([
4 ("system", "You translate to {language}."),
5 MessagesPlaceholder("history"),
6 ("human", "{text}"),
7])
8print(prompt.input_variables) # ['history', 'language', 'text']
9
10french = prompt.partial(language="French") # one variable fewer to supply
11messages = french.format_messages(history=[("human", "bonjour?"), ("ai", "bonjour")], text="hello")
12for m in messages:
13 print(m.type, "|", m.text)
14# system | You translate to French.
15# human | bonjour?
16# ai | bonjour
17# human | hello
StrOutputParser
A Runnable whose invoke accepts a BaseMessage or a str and returns a str: for a message it
returns .text, for a string the string itself. Its only job is to end a pipeline so the caller
gets plain text instead of an AIMessage; it also works on streams, passing each chunk's text
through. Core's other parsers follow the same BaseOutputParser shape but return structure:
JsonOutputParser parses the reply as JSON (tolerating partial JSON while streaming), and
PydanticOutputParser(pydantic_object=…) validates it into a model and adds
get_format_instructions() for the prompt.
1from langchain_core.language_models import GenericFakeChatModel
2from langchain_core.messages import AIMessage
3from langchain_core.output_parsers import StrOutputParser
4from langchain_core.prompts import ChatPromptTemplate
5
6prompt = ChatPromptTemplate.from_messages([("human", "{text}")])
7model = GenericFakeChatModel(messages=iter([AIMessage("bonjour")]))
8pipeline = prompt | model | StrOutputParser()
9
10print(repr(pipeline.invoke({"text": "hello"}))) # 'bonjour'
11print(repr(StrOutputParser().invoke(AIMessage("x")))) # 'x'
12print(repr(StrOutputParser().invoke("already text"))) # 'already text'