- 在继续之前收集保修信息。
- 将问题分类为硬件或软件问题。
- 提供解决方案或升级至人工支持。
- 在多次对话轮次中保持对话状态。
设置
安装
本教程需要langchain 包:
npm install langchain
LangSmith
设置 LangSmith 以检查代理内部正在发生的事情。然后设置以下环境变量:export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."
选择 LLM
从 LangChain 的集成套件中选择一个聊天模型:- OpenAI
- Anthropic
- Azure
- Google Gemini
- Bedrock Converse
👉 Read the OpenAI chat model integration docs
npm install @langchain/openai
import { initChatModel } from "langchain";
process.env.OPENAI_API_KEY = "your-api-key";
const model = await initChatModel("gpt-5.2");
👉 Read the Anthropic chat model integration docs
npm install @langchain/anthropic
import { initChatModel } from "langchain";
process.env.ANTHROPIC_API_KEY = "your-api-key";
const model = await initChatModel("claude-sonnet-4-6");
👉 Read the Azure chat model integration docs
npm install @langchain/azure
import { initChatModel } from "langchain";
process.env.AZURE_OPENAI_API_KEY = "your-api-key";
process.env.AZURE_OPENAI_ENDPOINT = "your-endpoint";
process.env.OPENAI_API_VERSION = "your-api-version";
const model = await initChatModel("azure_openai:gpt-5.2");
👉 Read the Google GenAI chat model integration docs
npm install @langchain/google-genai
import { initChatModel } from "langchain";
process.env.GOOGLE_API_KEY = "your-api-key";
const model = await initChatModel("google-genai:gemini-2.5-flash-lite");
👉 Read the AWS Bedrock chat model integration docs
npm install @langchain/aws
import { initChatModel } from "langchain";
// Follow the steps here to configure your credentials:
// https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
const model = await initChatModel("bedrock:gpt-5.2");
1. 定义自定义状态
首先,定义一个跟踪当前活动步骤的自定义状态架构:import { StateSchema } from "@langchain/langgraph";
import { z } from "zod";
// Define the possible workflow steps
const SupportStepSchema = z.enum(["warranty_collector", "issue_classifier", "resolution_specialist"]);
const WarrantyStatusSchema = z.enum(["in_warranty", "out_of_warranty"]);
const IssueTypeSchema = z.enum(["hardware", "software"]);
// State for customer support workflow
const SupportState = new StateSchema({
currentStep: SupportStepSchema.optional(),
warrantyStatus: WarrantyStatusSchema.optional(),
issueType: IssueTypeSchema.optional(),
});
current_step 字段是状态机模式的核心——它决定了每一轮加载哪个配置(提示 + 工具)。
2. 创建工作流状态管理工具
创建更新工作流状态的工具。这些工具允许代理记录信息并过渡到下一步。 关键在于使用Command 来更新状态,包括 current_step 字段:
import { z } from "zod";
import { tool, ToolMessage, type ToolRuntime } from "langchain";
import { Command } from "@langchain/langgraph";
const recordWarrantyStatus = tool(
async (input, config: ToolRuntime<typeof SupportState.State>) => {
return new Command({
update: {
messages: [
new ToolMessage({
content: `Warranty status recorded as: ${input.status}`,
tool_call_id: config.toolCallId,
}),
],
warrantyStatus: input.status,
currentStep: "issue_classifier",
},
});
},
{
name: "record_warranty_status",
description:
"Record the customer's warranty status and transition to issue classification.",
schema: z.object({
status: WarrantyStatusSchema,
}),
}
);
const recordIssueType = tool(
async (input, config: ToolRuntime<typeof SupportState.State>) => {
return new Command({
update: {
messages: [
new ToolMessage({
content: `Issue type recorded as: ${input.issueType}`,
tool_call_id: config.toolCallId,
}),
],
issueType: input.issueType,
currentStep: "resolution_specialist",
},
});
},
{
name: "record_issue_type",
description:
"Record the type of issue and transition to resolution specialist.",
schema: z.object({
issueType: IssueTypeSchema,
}),
}
);
const escalateToHuman = tool(
async (input) => {
// In a real system, this would create a ticket, notify staff, etc.
return `Escalating to human support. Reason: ${input.reason}`;
},
{
name: "escalate_to_human",
description: "Escalate the case to a human support specialist.",
schema: z.object({
reason: z.string(),
}),
}
);
const provideSolution = tool(
async (input) => {
return `Solution provided: ${input.solution}`;
},
{
name: "provide_solution",
description: "Provide a solution to the customer's issue.",
schema: z.object({
solution: z.string(),
}),
}
);
record_warranty_status 和 record_issue_type 如何返回 Command 对象,同时更新数据(warranty_status, issue_type)和 current_step。这就是状态机的工作原理——工具控制工作流程的进展。
3. 定义步骤配置
为每个步骤定义提示和工具。首先,为每个步骤定义提示:查看完整的提示定义
查看完整的提示定义
// Define prompts as constants for easy reference
const WARRANTY_COLLECTOR_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Warranty verification
At this step, you need to:
1. Greet the customer warmly
2. Ask if their device is under warranty
3. Use record_warranty_status to record their response and move to the next step
Be conversational and friendly. Don't ask multiple questions at once.`;
const ISSUE_CLASSIFIER_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Issue classification
CUSTOMER INFO: Warranty status is {warranty_status}
At this step, you need to:
1. Ask the customer to describe their issue
2. Determine if it's a hardware issue (physical damage, broken parts) or software issue (app crashes, performance)
3. Use record_issue_type to record the classification and move to the next step
If unclear, ask clarifying questions before classifying.`;
const RESOLUTION_SPECIALIST_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Resolution
CUSTOMER INFO: Warranty status is {warranty_status}, issue type is {issue_type}
At this step, you need to:
1. For SOFTWARE issues: provide troubleshooting steps using provide_solution
2. For HARDWARE issues:
- If IN WARRANTY: explain warranty repair process using provide_solution
- If OUT OF WARRANTY: escalate_to_human for paid repair options
Be specific and helpful in your solutions.`;
// Step configuration: maps step name to (prompt, tools, required_state)
const STEP_CONFIG = {
warranty_collector: {
prompt: WARRANTY_COLLECTOR_PROMPT,
tools: [recordWarrantyStatus],
requires: [],
},
issue_classifier: {
prompt: ISSUE_CLASSIFIER_PROMPT,
tools: [recordIssueType],
requires: ["warrantyStatus"],
},
resolution_specialist: {
prompt: RESOLUTION_SPECIALIST_PROMPT,
tools: [provideSolution, escalateToHuman],
requires: ["warrantyStatus", "issueType"],
},
} as const;
- 一目了然地查看所有步骤
- 添加新步骤(只需添加另一个条目)
- 理解工作流程依赖关系(
requires字段) - 使用带有状态变量的提示模板(例如
{warranty_status})变得容易
4. 创建基于步骤的中间件
创建从状态读取current_step 并应用适当配置的中间件。我们将使用 @wrap_model_call 装饰器来实现干净的代码:
import { createMiddleware } from "langchain";
const applyStepMiddleware = createMiddleware({
name: "applyStep",
stateSchema: SupportState,
wrapModelCall: async (request, handler) => {
// Get current step (defaults to warranty_collector for first interaction)
const currentStep = request.state.currentStep ?? "warranty_collector";
// Look up step configuration
const stepConfig = STEP_CONFIG[currentStep];
// Validate required state exists
for (const key of stepConfig.requires) {
if (request.state[key] === undefined) {
throw new Error(`${key} must be set before reaching ${currentStep}`);
}
}
// Format prompt with state values (supports {warrantyStatus}, {issueType}, etc.)
let systemPrompt: string = stepConfig.prompt;
for (const [key, value] of Object.entries(request.state)) {
systemPrompt = systemPrompt.replace(`{${key}}`, String(value ?? ""));
}
// Inject system prompt and step-specific tools
return handler({
...request,
systemPrompt,
tools: [...stepConfig.tools],
});
},
});
- 读取当前步骤:从状态获取
current_step(默认为 “warranty_collector”)。 - 查找配置:在
STEP_CONFIG中找到匹配的条目。 - 验证依赖项:确保存在所需的状态字段。
- 格式化提示:将状态值注入提示模板。
- 应用配置:覆盖系统提示和可用工具。
request.override() 方法很关键——它允许我们根据状态动态更改代理的行为,而无需创建单独的代理实例。
5. 创建代理
现在使用基于步骤的中间件和用于状态持久化的检查点创建代理:import { createAgent } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
// Collect all tools from all step configurations
const allTools = [
recordWarrantyStatus,
recordIssueType,
provideSolution,
escalateToHuman,
];
// Initialize the model
const model = new ChatOpenAI({
model: "gpt-4.1-mini",
temperature: 0.7,
});
// Create the agent with step-based configuration
const agent = createAgent({
model,
tools: allTools,
stateSchema: SupportState, # [!code highlight]
middleware: [applyStepMiddleware], # [!code highlight]
checkpointer: new MemorySaver(), # [!code highlight]
});
为什么需要检查点? 检查点在对话轮次之间维护状态。如果没有它,
current_step 状态将在用户消息之间丢失,从而破坏工作流程。6. 测试工作流
测试完整的工作流程:import { HumanMessage } from "@langchain/core/messages";
import { v4 as uuidv4 } from "uuid";
// Configuration for this conversation thread
const threadId = uuidv4();
const config = { configurable: { thread_id: threadId } };
// Turn 1: Initial message - starts with warranty_collector step
console.log("=== Turn 1: Warranty Collection ===");
let result = await agent.invoke(
{ messages: [new HumanMessage("Hi, my phone screen is cracked")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
// Turn 2: User responds about warranty
console.log("\n=== Turn 2: Warranty Response ===");
result = await agent.invoke(
{ messages: [new HumanMessage("Yes, it's still under warranty")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
console.log(`Current step: ${result.currentStep}`);
// Turn 3: User describes the issue
console.log("\n=== Turn 3: Issue Description ===");
result = await agent.invoke(
{ messages: [new HumanMessage("The screen is physically cracked from dropping it")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
console.log(`Current step: ${result.currentStep}`);
// Turn 4: Resolution
console.log("\n=== Turn 4: Resolution ===");
result = await agent.invoke(
{ messages: [new HumanMessage("What should I do?")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
- 保修验证步骤:询问保修状态
- 问题分类步骤:询问问题详情,确定是硬件问题
- 解决步骤:提供保修维修说明
7. 理解状态转换
让我们追踪每一轮发生了什么:第一轮:初始消息
{
messages: [new HumanMessage("Hi, my phone screen is cracked")],
currentStep: "warranty_collector" // Default value
}
- 系统提示:
WARRANTY_COLLECTOR_PROMPT - 工具:
[record_warranty_status]
第二轮:记录保修后
工具调用:recordWarrantyStatus("in_warranty") 返回:
new Command({
update: {
warrantyStatus: "in_warranty",
currentStep: "issue_classifier" // State transition!
}
})
- 系统提示:
ISSUE_CLASSIFIER_PROMPT(使用warranty_status="in_warranty"格式化) - 工具:
[record_issue_type]
第三轮:问题分类后
工具调用:recordIssueType("hardware") 返回:
new Command({
update: {
issueType: "hardware",
currentStep: "resolution_specialist" // State transition!
}
})
- 系统提示:
RESOLUTION_SPECIALIST_PROMPT(使用warranty_status和issue_type格式化) - 工具:
[provide_solution, escalate_to_human]
current_step 驱动工作流程,中间件响应并在下一轮应用适当的配置。
8. 管理消息历史
随着代理逐步推进,消息历史会增长。使用 摘要中间件 压缩早期消息,同时保留对话上下文:import { createAgent, SummarizationMiddleware } from "langchain"; # [!code highlight]
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model,
tools: allTools,
stateSchema: SupportState,
middleware: [
applyStepMiddleware,
new SummarizationMiddleware({ # [!code highlight]
model: "gpt-4.1-mini",
trigger: { tokens: 4000 },
keep: { messages: 10 },
}),
],
checkpointer: new MemorySaver(),
});
9. 增加灵活性:返回
某些工作流程需要允许用户返回到之前的步骤以更正信息(例如,更改保修状态或问题分类)。然而,并非所有转换都是有意义的——例如,一旦退款已处理,通常无法返回。对于这个支持工作流程,我们将添加工具以返回到保修验证和问题分类步骤。如果您的工作流程需要在大多数步骤之间进行任意转换,请考虑是否真的需要结构化工作流程。此模式最适合步骤遵循清晰的顺序进展,偶尔向后转换以进行更正的情况。
import { tool } from "langchain";
import { Command } from "@langchain/langgraph";
import { z } from "zod";
const goBackToWarranty = tool( # [!code highlight]
async () => {
return new Command({ update: { currentStep: "warranty_collector" } }); # [!code highlight]
},
{
name: "go_back_to_warranty",
description: "Go back to warranty verification step.",
schema: z.object({}),
}
);
const goBackToClassification = tool( # [!code highlight]
async () => {
return new Command({ update: { currentStep: "issue_classifier" } }); # [!code highlight]
},
{
name: "go_back_to_classification",
description: "Go back to issue classification step.",
schema: z.object({}),
}
);
// Update the resolution_specialist configuration to include these tools
STEP_CONFIG.resolution_specialist.tools.push(
goBackToWarranty,
goBackToClassification
);
const RESOLUTION_SPECIALIST_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Resolution
CUSTOMER INFO: Warranty status is {warranty_status}, issue type is {issue_type}
At this step, you need to:
1. For SOFTWARE issues: provide troubleshooting steps using provide_solution
2. For HARDWARE issues:
- If IN WARRANTY: explain warranty repair process using provide_solution
- If OUT OF WARRANTY: escalate_to_human for paid repair options
If the customer indicates any information was wrong, use:
- go_back_to_warranty to correct warranty status
- go_back_to_classification to correct issue type
Be specific and helpful in your solutions.`;
const result = await agent.invoke(
{ messages: [new HumanMessage("Actually, I made a mistake - my device is out of warranty")] },
config
);
// Agent will call go_back_to_warranty and restart the warranty verification step
完整示例
以下是所有内容的可运行脚本:Show 完整代码
Show 完整代码
import { createMiddleware, createAgent } from "langchain";
import { z } from "zod";
import { v4 as uuidv4 } from "uuid";
import { tool, ToolMessage, type ToolRuntime, HumanMessage } from "langchain";
import { Command, MemorySaver, StateSchema } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
// Define the possible workflow steps
const SupportStepSchema = z.enum([
"warranty_collector",
"issue_classifier",
"resolution_specialist",
]);
const WarrantyStatusSchema = z.enum(["in_warranty", "out_of_warranty"]);
const IssueTypeSchema = z.enum(["hardware", "software"]);
// State for customer support workflow
const SupportState = new StateSchema({
currentStep: SupportStepSchema.optional(),
warrantyStatus: WarrantyStatusSchema.optional(),
issueType: IssueTypeSchema.optional(),
});
const recordWarrantyStatus = tool(
async (input, config: ToolRuntime<typeof SupportState.State>) => {
return new Command({
update: {
messages: [
new ToolMessage({
content: `Warranty status recorded as: ${input.status}`,
tool_call_id: config.toolCallId,
}),
],
warrantyStatus: input.status,
currentStep: "issue_classifier",
},
});
},
{
name: "record_warranty_status",
description:
"Record the customer's warranty status and transition to issue classification.",
schema: z.object({
status: WarrantyStatusSchema,
}),
}
);
const recordIssueType = tool(
async (input, config: ToolRuntime<typeof SupportState.State>) => {
return new Command({
update: {
messages: [
new ToolMessage({
content: `Issue type recorded as: ${input.issueType}`,
tool_call_id: config.toolCallId,
}),
],
issueType: input.issueType,
currentStep: "resolution_specialist",
},
});
},
{
name: "record_issue_type",
description:
"Record the type of issue and transition to resolution specialist.",
schema: z.object({
issueType: IssueTypeSchema,
}),
}
);
const escalateToHuman = tool(
async (input) => {
// In a real system, this would create a ticket, notify staff, etc.
return `Escalating to human support. Reason: ${input.reason}`;
},
{
name: "escalate_to_human",
description: "Escalate the case to a human support specialist.",
schema: z.object({
reason: z.string(),
}),
}
);
const provideSolution = tool(
async (input) => {
return `Solution provided: ${input.solution}`;
},
{
name: "provide_solution",
description: "Provide a solution to the customer's issue.",
schema: z.object({
solution: z.string(),
}),
}
);
// Define prompts as constants for easy reference
const WARRANTY_COLLECTOR_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Warranty verification
At this step, you need to:
1. Greet the customer warmly
2. Ask if their device is under warranty
3. Use record_warranty_status to record their response and move to the next step
Be conversational and friendly. Don't ask multiple questions at once.`;
const ISSUE_CLASSIFIER_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Issue classification
CUSTOMER INFO: Warranty status is {warranty_status}
At this step, you need to:
1. Ask the customer to describe their issue
2. Determine if it's a hardware issue (physical damage, broken parts) or software issue (app crashes, performance)
3. Use record_issue_type to record the classification and move to the next step
If unclear, ask clarifying questions before classifying.`;
const RESOLUTION_SPECIALIST_PROMPT = `You are a customer support agent helping with device issues.
CURRENT STAGE: Resolution
CUSTOMER INFO: Warranty status is {warranty_status}, issue type is {issue_type}
At this step, you need to:
1. For SOFTWARE issues: provide troubleshooting steps using provide_solution
2. For HARDWARE issues:
- If IN WARRANTY: explain warranty repair process using provide_solution
- If OUT OF WARRANTY: escalate_to_human for paid repair options
Be specific and helpful in your solutions.`;
// Step configuration: maps step name to (prompt, tools, required_state)
const STEP_CONFIG = {
warranty_collector: {
prompt: WARRANTY_COLLECTOR_PROMPT,
tools: [recordWarrantyStatus],
requires: [],
},
issue_classifier: {
prompt: ISSUE_CLASSIFIER_PROMPT,
tools: [recordIssueType],
requires: ["warrantyStatus"],
},
resolution_specialist: {
prompt: RESOLUTION_SPECIALIST_PROMPT,
tools: [provideSolution, escalateToHuman],
requires: ["warrantyStatus", "issueType"],
},
} as const;
const applyStepMiddleware = createMiddleware({
name: "applyStep",
stateSchema: SupportState,
wrapModelCall: async (request, handler) => {
// Get current step (defaults to warranty_collector for first interaction)
const currentStep = request.state.currentStep ?? "warranty_collector";
// Look up step configuration
const stepConfig = STEP_CONFIG[currentStep];
// Validate required state exists
for (const key of stepConfig.requires) {
if (request.state[key] === undefined) {
throw new Error(`${key} must be set before reaching ${currentStep}`);
}
}
// Format prompt with state values (supports {warrantyStatus}, {issueType}, etc.)
let systemPrompt: string = stepConfig.prompt;
for (const [key, value] of Object.entries(request.state)) {
systemPrompt = systemPrompt.replace(`{${key}}`, String(value ?? ""));
}
// Inject system prompt and step-specific tools
return handler({
...request,
systemPrompt,
tools: [...stepConfig.tools],
});
},
});
// Collect all tools from all step configurations
const allTools = [
recordWarrantyStatus,
recordIssueType,
provideSolution,
escalateToHuman,
];
const model = new ChatOpenAI({
model: "gpt-4.1-mini",
});
// Create the agent with step-based configuration
const agent = createAgent({
model,
tools: allTools,
middleware: [applyStepMiddleware],
checkpointer: new MemorySaver(),
});
// Configuration for this conversation thread
const threadId = uuidv4();
const config = { configurable: { thread_id: threadId } };
// Turn 1: Initial message - starts with warranty_collector step
console.log("=== Turn 1: Warranty Collection ===");
let result = await agent.invoke(
{ messages: [new HumanMessage("Hi, my phone screen is cracked")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
// Turn 2: User responds about warranty
console.log("\n=== Turn 2: Warranty Response ===");
result = await agent.invoke(
{ messages: [new HumanMessage("Yes, it's still under warranty")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
console.log(`Current step: ${result.currentStep}`);
// Turn 3: User describes the issue
console.log("\n=== Turn 3: Issue Description ===");
result = await agent.invoke(
{ messages: [new HumanMessage("The screen is physically cracked from dropping it")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
console.log(`Current step: ${result.currentStep}`);
// Turn 4: Resolution
console.log("\n=== Turn 4: Resolution ===");
result = await agent.invoke(
{ messages: [new HumanMessage("What should I do?")] },
config
);
for (const msg of result.messages) {
console.log(msg.content);
}
下一步
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

