Tutorial

LangChain and LangGraph

Give a LangGraph agent your messaging channels, and let it answer incoming messages, in about ten minutes.

What you'll build

A LangGraph agent that texts a test number with VoiceLayer's message_send tool, then answers a simulated incoming text from your own process. You use @voicelayer/agents/langchain, which loads the VoiceLayer channel tools as LangChain tools over MCP.

Prerequisites

  • Node.js 22 or later, and TypeScript 5.2 or later if you compile your own (the examples run with tsx). The examples are ES modules with top-level await, and use await using, which closes a connection when the file ends (TypeScript 5.2 or later with esnext.disposable in lib; or close it yourself with await tools[Symbol.asyncDispose]()).
  • A test key in VOICELAYER_API_KEY (vl_test_…): see Authentication and API keys. With a test key every send goes to a simulated carrier and costs nothing.
  • An OpenAI API key in OPENAI_API_KEY (the quickstart uses ChatOpenAI; any LangChain chat model works).
  • No phone number needed: with a test key, your workspace's free test sender stands in for one.

Install

npm install @voicelayer/agents @langchain/core@1 @langchain/langgraph@1 @langchain/mcp-adapters@1 @langchain/openai@1

Set the keys once. VoiceLayer's helpers read VOICELAYER_API_KEY themselves, so no example passes a key:

export VOICELAYER_API_KEY=vl_test_...
export OPENAI_API_KEY=...
npm pkg set type=module

Give your agent VoiceLayer's tools

Save this as agent.ts.

agent.ts
import { ChatOpenAI } from '@langchain/openai';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { voicelayerTools } from '@voicelayer/agents/langchain';

await using tools = await voicelayerTools();
const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4.1-mini' }), tools, prompt: 'You answer customers in one short sentence.' });

const result = await agent.invoke({ messages: [{ role: 'user', content: 'Text "Hello from VoiceLayer" to +15550101 over sms.' }] });
console.log(result.messages.at(-1)?.content);
  • voicelayerTools() connects to VoiceLayer with the LangChain MCP adapters and returns the tools as LangChain tools. await using closes the connection when the file ends.
  • createReactAgent builds a LangGraph agent with those tools. invoke() asks it to text +15550101, a reserved test number.

Run it:

npx --yes tsx agent.ts

The agent texts the test number and prints its answer.

Answer incoming messages

Direct mode: your process answers the conversations VoiceLayer hands to code. Save this as serve.ts.

serve.ts
import { ChatOpenAI } from '@langchain/openai';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { serveDirect } from '@voicelayer/agents/langchain';

const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4.1-mini' }), tools: [], prompt: 'You answer customers in one short sentence.' });
await serveDirect(agent);

serveDirect(agent) answers incoming messages with the graph: each message in a conversation your code controls becomes one invocation, and the reply is sent back in that conversation. It keeps running until you stop the process.

npx --yes tsx serve.ts
voicelayer: answering incoming messages (Ctrl+C to stop)

Test it without a person

With the server from the last step running, open a second terminal and simulate a person texting you. Save this as simulate.ts.

simulate.ts
import { simulateInbound } from '@voicelayer/agents';

const { conversationId } = await simulateInbound({ text: 'What are your opening hours?' });
console.log(`Simulated a text from +15550101 in conversation ${conversationId}`);
npx --yes tsx simulate.ts

The first terminal prints voicelayer: replied in conversation …. Open Conversations in the dashboard and turn on test conversations to see the text and the reply. Both went to the simulated carrier.

Direct mode answers conversations your code controls (api). A new workspace's free test sender is yours by default; if the number you use has an agent that answers texts, hand the conversation to your code first.

To answer real people, swap the test key for a live key. Nothing else changes.

Handle errors

A missing key stops the program at start with a message that names VOICELAYER_API_KEY. A wrong key, or anything else the API refuses, comes back as an error with a stable code:

ErrorStatusHow to fix
unauthorized401Send Authorization: Bearer <key> with a secret key from the dashboard (Developers → API keys). Test keys start with vl_test_.
insufficient_scope403Use a key with the scope named in details.required, or one whose restriction covers this channel or Binding.
test_address_required422Use a reserved test address, or a live key to reach a real person.
test_mode_only403Use a test key (vl_test_…) from the dashboard.
test_limit_reached429Try again after midnight UTC, or ask for a higher test limit.

If the first terminal never prints a reply, the conversation is answered by your workspace's agent, not by your code. See Hand off a conversation.

Next steps

On this page