langchain-core reference
langchain Packages
- Version of langchain-core referenced here:
langchain-core==1.6.3(released 2026-09-11). - The examples were derived by inspecting the installed package and executed against it on Python 3.13
- This page is a part of a five-page set (see
how the LangChain stack fits together for how
langchain-core,langgraphandlangchainfit together.) - the dependency arrow points one way:
1langchain ──depends on──▶ langgraph ──depends on──▶ langchain-core
What langchain-core is
langchain-coreis the package that defines the data types the rest of the ecosystem uses, and the base classes the other packages subclass:- It defines:
- the messages a conversation is made of
- what a chat model and a tool are
- the
Runnablecalling convention they share - and the prompt templates and output parsers on either end of a chain.
- The index below walks that inventory module by module.
- This page is just a selection, not covering the whole package. The langchain-core package contains many more classes.
- The examples were chosen because they are what you actually hold while writing an agent or a RAG chain.
- A LangGraph agent can accept a chat model from any provider because every provider's class subclasses the
BaseChatModeldefined here, and every node exchanges the message classes defined here. - langchain-core 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)
- an HTTP client for any model provider (that is
Index
One group per module — six of the package's 33 — in the order the sections below appear.
| Module | Name | What it defines / does | Most-used methods & fields |
|---|---|---|---|
langchain_corelanguage_models | 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 |
langchain_coremessages | 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 | |
langchain_coretools | @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 | |
langchain_corerunnables | 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 |
langchain_coreprompts | 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 |
langchain_coreoutput_parsers | StrOutputParser | Runnable that takes a message (or str) and returns its .text, so a pipeline ends in a plain string | invoke, parse |
Two more modules show up in the examples without an entry of their own: langchain_core.outputs
(ChatResult, ChatGeneration — the wrapper a model's _generate returns), and
langchain_core.callbacks with .tracers, the hooks that fire on every start/token/end event and
that LangSmith plugs into.
langchain_core.language_models
What a chat model is, as an abstract class. It is an interface, not a client: ChatAnthropic and ChatOpenAI live in langchain-anthropic and langchain-openai and subclass the class defined here, which is why an agent written against it runs on either provider. The module also ships scripted fakes (GenericFakeChatModel) so examples and tests run with no API key and no network.
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.
langchain_core.messages
The messages exchanged between a model and the harness driving it (your own code, or an agent loop). One class per role: the harness sends a SystemMessage and a HumanMessage, the model answers with an AIMessage, and when that answer asks for a tool (AIMessage.tool_calls) the harness runs it and appends a ToolMessage with the result. A conversation is nothing more than a list of these, handed to the model whole on every turn.
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']
langchain_core.tools
How a Python function is described to a model and called back. A tool is a name, a description and a JSON-schema argument list — read off the function's name, docstring and type hints — plus the plumbing that validates the model's arguments, runs the function and returns a ToolMessage. The model itself executes nothing: it emits a tool_calls entry naming a tool, and the harness invokes the matching one.
@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
langchain_core.runnables
The single calling convention everything else on this page obeys. Models, tools, prompts and parsers are all Runnable, so each answers invoke/batch/stream, and \| chains them into a RunnableSequence (prompt \| model \| StrOutputParser()). The module also holds the parts you compose with: RunnableLambda to drop a plain function into a chain, and RunnableConfig, the per-call dict carrying callbacks, tags and recursion limits.
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
langchain_core.prompts
Templates that turn variables into messages, so the wording of a prompt lives in one place instead of in string concatenation. ChatPromptTemplate fills {placeholders} in a list of (role, template) pairs; MessagesPlaceholder splices a whole conversation history into the middle of one; PromptTemplate is the plain-string equivalent.
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
langchain_core.output_parsers
The far end of a chain: turn the model's reply into something other than a message. StrOutputParser gives plain text, JsonOutputParser a dict (tolerating half-arrived JSON while streaming), PydanticOutputParser an object validated against your schema.
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'