LangChain / LangGraph
A Prompting Press composition resolves to an ordered list of role-tagged messages —
each with a role (system / user / assistant) and the rendered text. LangChain
accepts plain { role, content } messages directly at invoke, in a LangGraph
MessagesState, and through add_messages. So the bridge is a one-line key rename:
text becomes content, and the role passes straight through (LangChain treats user
and assistant identically to its human / ai aliases).
Feed the resulting list straight to your model or graph. Prompting Press already did the templating, so there is no reason to route it back through a LangChain prompt template.
Map a composition to LangChain messages
Section titled “Map a composition to LangChain messages”"""Use Prompting Press prompts with LangChain / LangGraph (Python).
Prompting Press renders a composition to an ordered list of role-taggedmessages. LangChain accepts plain ``{"role", "content"}`` dicts directly, so thebridge is a one-line key rename (``text`` -> ``content``): map each renderedmessage and hand the list straight to a chat model or a LangGraph node.
Note: do NOT route already-rendered text through``ChatPromptTemplate.from_messages`` with the tuple/dict shorthand. That pathtreats ``content`` as an f-string template and raises on any literal ``{...}``(e.g. JSON) in your rendered text. Prompting Press already did the templating —feed the model/graph the messages directly. Standalone."""
from langchain_core.language_models.fake_chat_models import FakeListChatModelfrom langchain_core.messages import AIMessage, HumanMessage, SystemMessagefrom langchain_core.messages.utils import convert_to_messagesfrom prompting_press import Composition, Promptfrom pydantic import BaseModel
class SysVars(BaseModel): instructions: str
class UserVars(BaseModel): payload: str
def to_langchain(messages): """Map a Prompting Press composition result to LangChain message dicts.
``[{role, text}]`` -> ``[{"role", "content"}]``. Order and role are preserved; ``role`` values (system/user/assistant) are accepted by LangChain as-is. """ return [{"role": m.role, "content": m.text} for m in messages]
# Optional: if you want typed message OBJECTS instead of dicts, map role -> class._ROLE_TO_MESSAGE = { "system": SystemMessage, "user": HumanMessage, "assistant": AIMessage,}
def to_langchain_objects(messages): return [_ROLE_TO_MESSAGE[m.role](content=m.text) for m in messages]
# Build a composition. The user turn deliberately contains literal braces to# prove rendered text is NOT re-templated by the direct path.sys_prompt = Prompt( { "name": "system-preamble", "role": "system", "body": "{{ instructions }}", "variables": {"instructions": {"type": "string", "trusted": True}}, })user_prompt = Prompt( { "name": "user-turn", "role": "user", "body": "{{ payload }}", "variables": {"payload": {"type": "string", "trusted": False}}, })
comp = Composition()comp.append(sys_prompt, SysVars(instructions="You are a helpful assistant."))comp.append(user_prompt, UserVars(payload='Return this exactly: {"k": 1}'))
lc_messages = to_langchain(comp.resolve())
# Hand the messages straight to a chat model (FakeListChatModel stands in for a# real one so this runs offline — a real app uses ChatOpenAI, ChatBedrock, ...).model = FakeListChatModel(responses=["ok"])reply = model.invoke(lc_messages)print(reply.content) # "ok"
# --- assertions (this file is executed by CI) ---
# Key rename only: order + role preserved, content == text verbatim.assert lc_messages == [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": 'Return this exactly: {"k": 1}'},]
# LangChain coerces the dicts to the right message classes, and the literal# braces survive (they would raise under ChatPromptTemplate's template path).coerced = convert_to_messages(lc_messages)assert [type(m).__name__ for m in coerced] == ["SystemMessage", "HumanMessage"]assert coerced[1].content == 'Return this exactly: {"k": 1}'
# The typed-object variant maps to the same classes.objs = to_langchain_objects(comp.resolve())assert [type(m).__name__ for m in objs] == ["SystemMessage", "HumanMessage"]assert isinstance(reply, AIMessage)The whole bridge is [{"role": m.role, "content": m.text} for m in messages]. LangChain’s
own convert_to_messages (used internally by invoke) turns those dicts into
SystemMessage / HumanMessage / AIMessage. If you prefer typed message objects — for
example to append to LangGraph state explicitly — map the role to the class instead, as the
to_langchain_objects helper shows.
// Use Prompting Press prompts with LangChain / LangGraph (TypeScript).//// Prompting Press renders a composition to an ordered list of role-tagged// messages. LangChain accepts plain `{ role, content }` objects directly, so// the bridge is a one-line key rename (`text` -> `content`): map each rendered// message and hand the list straight to a chat model or a LangGraph node.//// Note: do NOT route already-rendered text through// `ChatPromptTemplate.fromMessages` with the tuple/object shorthand — that path// treats `content` as a template and breaks on literal `{...}` (e.g. JSON) in// your rendered text. Prompting Press already did the templating; feed the// model/graph the messages directly.import assert from "node:assert/strict";import { test } from "node:test";import { z } from "zod";import { Composition, Prompt } from "prompting-press";import { AIMessage, HumanMessage, SystemMessage, coerceMessageLikeToMessage,} from "@langchain/core/messages";
const SysVars = z.object({ instructions: z.string().min(1) });const UserVars = z.object({ payload: z.string().min(1) });
// [{ role, text }] -> [{ role, content }]. Order + role preserved; LangChain// accepts the role strings (system/user/assistant) as-is.function toLangchain(messages: { role: string; text: string }[]) { return messages.map((m) => ({ role: m.role, content: m.text }));}
const sysPrompt = new Prompt({ name: "system-preamble", role: "system", body: "{{ instructions }}", variables: { instructions: { type: "string", trusted: true } },});const userPrompt = new Prompt({ name: "user-turn", role: "user", body: "{{ payload }}", variables: { payload: { type: "string", trusted: false } },});
test("map a Prompting Press composition to LangChain messages", () => { const comp = Composition.fromMessages([ { prompt: sysPrompt, schema: SysVars, data: { instructions: "You are a helpful assistant." } }, // Literal braces in the rendered text prove it is NOT re-templated. { prompt: userPrompt, schema: UserVars, data: { payload: 'Return this exactly: {"k": 1}' } }, ]);
const lcMessages = toLangchain(comp.resolve());
// Key rename only: order + role preserved, content === text verbatim. assert.deepEqual(lcMessages, [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: 'Return this exactly: {"k": 1}' }, ]);
// LangChain coerces the objects to the right message classes; the literal // braces survive (they would break ChatPromptTemplate's template path). const coerced = lcMessages.map(coerceMessageLikeToMessage); assert.deepEqual( coerced.map((m) => m.constructor.name), ["SystemMessage", "HumanMessage"], ); assert.equal(coerced[1].content, 'Return this exactly: {"k": 1}');
// A real app would then call `await model.invoke(lcMessages)` (ChatOpenAI, // ChatBedrock, ...) or seed a LangGraph MessagesState with `lcMessages`. // Both accept the role-object list directly — no ChatPromptTemplate needed. void [AIMessage, HumanMessage, SystemMessage];});messages.map(m => ({ role: m.role, content: m.text })) is the entire bridge.
coerceMessageLikeToMessage (what the chat model applies internally) turns each object
into the matching message class, and literal braces in the text are preserved.
In LangGraph
Section titled “In LangGraph”Prompts in LangGraph are just messages in state. Seed a MessagesState with the mapped
list, or return it from a node — the same { role, content } objects are accepted there
too. There is nothing framework-specific to learn beyond the message shape above.
docs current as of 0.2.0