langchain-core reference

langchain Packages

1langchain  ──depends on──▶  langgraph  ──depends on──▶  langchain-core

What langchain-core is

  • langchain-core is 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 Runnable calling 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 BaseChatModel defined 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)

Index

One group per module — six of the package's 33 — in the order the sections below appear.

ModuleNameWhat it defines / doesMost-used methods & fields
langchain_core
language_models
BaseChatModelAbstract class: a subclass implements _generate and _llm_type; in return it inherits invoke/stream/batch, tool binding and structured outputinvoke, stream, bind_tools, with_structured_output
langchain_core
messages
HumanMessageMessage with type="human"; content is a string or a list of content blocks; .text flattens eithercontent, text, content_blocks
AIMessageMessage with type="ai" plus tool_calls (what the model wants run), invalid_tool_calls and usage_metadatatext, tool_calls, usage_metadata
ToolMessageMessage 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
SystemMessageMessage with type="system"; providers send it as the system prompt, outside the user/assistant turnscontent, text
langchain_core
tools
@toolBuilds a StructuredTool from a function: name from the function name, description from the docstring, argument schema from the type hintsinvoke, name, description, args
BaseToolAbstract Runnable: a subclass implements _run; it gets invoke (dict or tool-call in, ToolMessage out), schema generation and error handlinginvoke, args, tool_call_schema, handle_tool_error
langchain_core
runnables
RunnableThe calling convention shared by models, tools, prompts and parsers: invoke/batch/stream, async twins, | composition, retries and fallbacksinvoke, batch, stream, pipe (|), with_retry
langchain_core
prompts
ChatPromptTemplateHolds (role, template) pairs; format_messages(**vars) fills {placeholders} and returns message objects; MessagesPlaceholder splices a history list infrom_messages, format_messages, partial, invoke
langchain_core
output_parsers
StrOutputParserRunnable that takes a message (or str) and returns its .text, so a pipeline ends in a plain stringinvoke, 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. invoke accepts a str (wrapped in a HumanMessage), a list of messages, a list of (role, text) tuples or dicts, or a PromptValue, and hands _generate a list of BaseMessage objects. It returns one AIMessage.
  • Runnable methods. invoke, batch, stream and the async ainvoke/abatch/astream. If the subclass does not override _stream, stream falls back to calling _generate once and yielding a single chunk.
  • bind_tools(tools, tool_choice=None) — returns a new Runnable with tool schemas attached to each request, so the model can answer with tool_calls. The base implementation raises NotImplementedError; every provider package overrides it.
  • with_structured_output(schema, include_raw=False) — returns a Runnable whose output is a dict or Pydantic object matching schema instead of an AIMessage. Also provider-implemented.
  • Bookkeeping fields: cache, rate_limiter, callbacks, tags, metadata, disable_streaming, and get_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.

↩ back to index


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

↩ back to index


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. args is already parsed into a Python dict. When a model calls tools, content is 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 an error.
  • usage_metadata — {"input_tokens", "output_tokens", "total_tokens"} plus optional input_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

↩ back to index


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 the id of the AIMessage.tool_calls entry it answers; providers reject a conversation where a call has no matching result.
  • status — "success" (default) or "error". BaseTool.invoke sets "error" when the tool raised and handle_tool_error is on; the model sees the error text as content.
  • 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 uses response_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]

↩ back to index


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']

↩ back to index


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=True also reads a Google-style Args: 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

↩ back to index


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 from AIMessage.tool_calls — runs it and returns a ready-made ToolMessage carrying the matching tool_call_id. Agent loops use the second form.
  • Schema. args (the JSON-schema properties) and tool_call_schema (a Pydantic model), derived from _run's signature or from args_schema; bind_tools sends this to the model.
  • Error handling. With handle_tool_error=True, a ToolException raised inside _run becomes a ToolMessage with status="error" instead of propagating; a string or callable there customises the error text. handle_validation_error does 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

↩ back to index


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 twins ainvoke, abatch, astream. A subclass implements invoke and the rest derive from it unless overridden (batch runs in a thread pool; stream yields one item).
  • Composition: a | b builds a RunnableSequence that feeds a's output to b; 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 object CompiledStateGraph.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

↩ back to index


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 the Runnable interface, which is how a template becomes the first stage of prompt | 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

↩ back to index


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'

↩ back to index