LangChain.js prompt + model + parser chain tested with a fake chat model: working versions (tested 2026-10-15)
Compose a ChatPromptTemplate, chat model and output parser with LCEL in @langchain/core, tested offline with FakeListChatModel.
passed4 passed · 0 failed2026-10-15 13:31 UTC- Status
- passed
- Last passed
- 2026-10-15 13:31 UTC
- Tests
- 4 / 4
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38742087302
The code
Exactly the files that passed. Your agent can pull them with get_recipe("langchain-core-fake-model-chain") (install), or see them on GitHub.
{
"name": "langchain-core-fake-model-chain",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"@langchain/core": "1.2.17",
"zod": "4.6.7"
},
"devDependencies": {
"tsx": "4.23.15",
"@types/node": "24.19.2"
}
}
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
import { z } from "zod";
const prompt = ChatPromptTemplate.fromMessages([
["system", "Classify the support ticket. Reply with JSON: {{\"category\": \"billing\"|\"bug\"|\"other\", \"urgent\": boolean}}"],
["human", "{ticket}"],
]);
const Result = z.object({ category: z.enum(["billing", "bug", "other"]), urgent: z.boolean() });
export function buildClassifier(model: BaseChatModel) {
const chain = prompt.pipe(model).pipe(new StringOutputParser());
return async (ticket: string) => {
const raw = await chain.invoke({ ticket });
const json = raw.replace(/^```(json)?\s*|```\s*$/g, "").trim(); // models often wrap JSON in fences
return Result.parse(JSON.parse(json));
};
}
export const formatPrompt = (ticket: string) => prompt.formatMessages({ ticket });
import { test } from "node:test";
import assert from "node:assert/strict";
import { FakeListChatModel } from "@langchain/core/utils/testing";
import { buildClassifier, formatPrompt } from "../src/chain.ts";
test("parses plain JSON output", async () => {
const classify = buildClassifier(new FakeListChatModel({ responses: ['{"category":"billing","urgent":true}'] }));
assert.deepEqual(await classify("I was charged twice!"), { category: "billing", urgent: true });
});
test("strips markdown fences", async () => {
const classify = buildClassifier(new FakeListChatModel({ responses: ['```json\n{"category":"bug","urgent":false}\n```'] }));
assert.deepEqual(await classify("button broken"), { category: "bug", urgent: false });
});
test("rejects out-of-schema output", async () => {
const classify = buildClassifier(new FakeListChatModel({ responses: ['{"category":"sales","urgent":"yes"}'] }));
await assert.rejects(classify("hi"));
});
test("prompt escapes literal braces and fills the variable", async () => {
const msgs = await formatPrompt("hello");
assert.match(String(msgs[0].content), /"category"/);
assert.equal(msgs[1].content, "hello");
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
LangChain.js tools with Zod schemas and a manual tool-calling loop
Define LangChain.js tools with tool() and Zod schemas, run model tool_calls through them, and feed ToolMessages back, tested with a scripted fake chat model.
Anthropic TypeScript SDK tool use loop, tested with a mocked fetch
Run a Claude tool_use loop with the official Anthropic SDK and Zod-validated tool inputs, tested offline with an injected fetch.
better-sqlite3 transactions with Zod-validated inputs
A small ledger on better-sqlite3 using db.transaction for atomic transfers, nested transactions as savepoints for all-or-nothing batches, and Zod 4 to validate inputs and rows, tested on in-memory SQLite.
Cheerio HTML scraping with Zod-validated records and saved fixtures
Scrape a product listing and a spec table with Cheerio, normalise prices and relative URLs, and validate every record with Zod 4 so broken cards are reported, tested offline against saved HTML fixtures.