BaseChatModel: a model of your own
A custom chat model is a subclass of BaseChatModel that supplies a type name and a _generate method, and LangChain then treats it like any hosted model.
Last updated: 27 Sep, 2026 · LangChain 1.4
Every chat model in LangChain, from OpenAI's to Groq's, is a subclass of BaseChatModel. A subclass has to supply two things: a name for its type, and _generate, which turns messages into a reply. Everything else, including invoke, comes from the base class.
Subclassing BaseChatModel
from langchain.chat_models import BaseChatModel
class MyModel(BaseChatModel):
@property
def _llm_type(self): # a label for logs and traces
return "my-model"
def _generate(self, messages, stop=None, run_manager=None, **kwargs):
... # return a ChatResult holding one AIMessageSetting up the model class
Start with the imports and a name for the model's type.
import re
from langchain.chat_models import BaseChatModel
from langchain.messages import AIMessage
from langchain_core.outputs import ChatGeneration, ChatResult
class ShopModel(BaseChatModel):
@property
def _llm_type(self):
return "shop"_llm_type is a label LangChain uses in logs and traces. The imports are the base class, the message class the model returns, and two small wrappers that _generate has to put its reply in.
Writing the _generate method
Now the reply. _generate reads the messages and returns one AIMessage, wrapped in the result type every chat model returns.
def _generate(self, messages, stop=None, run_manager=None, **kwargs):
text = messages[-1].text
orders = re.findall(r"\b[A-Z]\d+\b", text)
if orders:
reply = f"I have no way to look up {orders[0]} yet."
else:
reply = "Hello. Which order is this about?"
message = AIMessage(reply)
return ChatResult(generations=[ChatGeneration(message=message)])The model reads the last message and looks for an order id, a capital letter followed by digits. With one, it admits it cannot look the order up yet. Without one, it asks which order the customer means. The reply goes into an AIMessage, wrapped in the result type every chat model returns.
Save both pieces as shop_model.py. Lesson 4 imports it, and lesson 6 teaches it to use tools.
Running the whole model
Create the model from the class above (saved as shop_model.py) and call it.
model = ShopModel()
reply = model.invoke("Where is my order A17?")
print(type(reply).__name__)
print(reply.text)invoke came from the base class. It turned the string into a HumanMessage, called your _generate, and returned the AIMessage inside the result.
Where invoke came from
invokeis not something you wrote; it comes fromBaseChatModel.- It turned the plain string into a
HumanMessagebefore calling your_generate. - It returned the
AIMessagefrom inside theChatResult, so the printed class isAIMessage.
A list of messages works too
model = ShopModel()
reply = model.invoke([
{"role": "system", "content": "You help customers of a small online shop."},
{"role": "user", "content": "Hello"},
])
print(reply.text)A list of dictionaries works too, converted to message objects before _generate sees them. The model reads only the last one, the customer's "Hello", so it asks which order this is about.
_generate sends the messages to the provider's servers and wraps what comes back. Yours decides with a few lines of Python. The rest of LangChain cannot tell the two apart, which is what lets every run below work without a key.Your model vs a hosted model
| Your model | Hosted model | |
|---|---|---|
| Where the reply comes from | A few lines of Python | The provider's servers |
| Needs an API key | No | Yes |
usage_metadata | None | Filled with token counts |
| Rest of LangChain | Treats it the same | Treats it the same |
When to write your own model
- Running every lesson here with no provider account.
- A deterministic stand-in in tests, so a run's output never drifts.
- A canned model while you build and check the code around it.
_llm_type and _generate. Leave either out and creating the model raises TypeError for the missing abstract method before it ever runs.Related
- Previous: Content blocks: images and files in a message
- Next: invoke, batch and stream
- Reference: Chat models
- Invoke it with "Hi, is B22 on its way?" and check which order it names.
- Change the reply for an order id so it includes every id it found.
- Delete the
_llm_typeproperty and read the error when you create the model.
This is what real progress feels like.