Whetstone.
Models and Structured OutputChat models and messages
Module 1, Lesson 120 min

Chat models and messages

Chronicler already classifies Discord messages with an LLM, through hand-rolled plumbing in @lumiere/openai-json: a four-attempt retry loop, markdown-fence stripping, a manual zod safeParse. This module gives you a grounded verdict on whether LangChain’s structured-output primitives can replace that dance, and along the way you learn to read any LangChain code fluently.

A chat model is a function from messages to a message. In LangChain the model is an object you construct once and call many times.

Constructing a chat model, two ways
import { ChatAnthropic } from "@langchain/anthropic";
import { initChatModel } from "langchain";

const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });

// or provider-agnostic, resolved from a string:
const model2 = await initChatModel("anthropic:claude-opus-4-8");

initChatModel is the indirection layer: you pass "provider:model" and it constructs the right class. This earns its keep when the provider is config, not code.

Messages are the input. A conversation is an array of message objects: HumanMessage, AIMessage, SystemMessage, ToolMessage. You call await model.invoke(messages) and get back an AIMessage. That is the whole contract at this level.

Practice

Try it yourself

Recall

The invoke contract

The asymmetry between the two sides is the part worth remembering.

What goes into model.invoke(...) and what comes back?

Reveal answer

An array of message objects (HumanMessage / AIMessage / SystemMessage / ToolMessage) goes in; a single AIMessage comes back.

Quiz

Why initChatModel

You have a codebase that needs to swap model providers based on a config file, not a code change. Which construction do you reach for?

  1. Anew ChatAnthropic({ model: ... })
  2. BinitChatModel("provider:model") resolved from config
  3. CHand-roll a switch statement over provider name
  4. DIt doesn't matter, they're identical
Show answer

Correct answer: B — initChatModel("provider:model") resolved from config

initChatModel takes a "provider:model" string and resolves the right class for you. That is exactly the indirection you want when the provider is a config value rather than a compile-time choice.

Sign in to track your progress →