状态机模式 描述了智能体行为在任务不同状态间转换的工作流。本教程展示如何通过使用工具调用动态更改单个智能体的配置来实现状态机——根据当前状态更新其可用工具和指令。状态可以由多种来源确定:智能体的过去操作(工具调用)、外部状态(如 API 调用结果),甚至是初始用户输入(例如,通过运行分类器来确定用户意图)。在本教程中,您将构建一个执行以下操作的客户支持智能体:
import { initChatModel } from "langchain";process.env.GOOGLE_API_KEY = "your-api-key";const model = await initChatModel("google-genai:gemini-2.5-flash-lite");
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";const model = new ChatGoogleGenerativeAI({ model: "gemini-2.5-flash-lite", apiKey: "your-api-key"});
import { StateSchema } from "@langchain/langgraph";import { z } from "zod";// Define the possible workflow stepsconst 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 workflowconst SupportState = new StateSchema({ currentStep: SupportStepSchema.optional(), warrantyStatus: WarrantyStatusSchema.optional(), issueType: IssueTypeSchema.optional(),});
// Define prompts as constants for easy referenceconst WARRANTY_COLLECTOR_PROMPT = `You are a customer support agent helping with device issues.CURRENT STAGE: Warranty verificationAt this step, you need to:1. Greet the customer warmly2. Ask if their device is under warranty3. Use record_warranty_status to record their response and move to the next stepBe 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 classificationCUSTOMER INFO: Warranty status is {warranty_status}At this step, you need to:1. Ask the customer to describe their issue2. 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 stepIf unclear, ask clarifying questions before classifying.`;const RESOLUTION_SPECIALIST_PROMPT = `You are a customer support agent helping with device issues.CURRENT STAGE: ResolutionCUSTOMER 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_solution2. For HARDWARE issues: - If IN WARRANTY: explain warranty repair process using provide_solution - If OUT OF WARRANTY: escalate_to_human for paid repair optionsBe specific and helpful in your solutions.`;
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], }); },});
import { createAgent } from "langchain";import { MemorySaver } from "@langchain/langgraph";import { ChatOpenAI } from "@langchain/openai";// Collect all tools from all step configurationsconst allTools = [ recordWarrantyStatus, recordIssueType, provideSolution, escalateToHuman,];// Initialize the modelconst model = new ChatOpenAI({ model: "gpt-5.4-mini", temperature: 0.7,});// Create the agent with step-based configurationconst agent = createAgent({ model, tools: allTools, stateSchema: SupportState, middleware: [applyStepMiddleware], checkpointer: new MemorySaver(),});
import { tool } from "langchain";import { Command } from "@langchain/langgraph";import { z } from "zod";const goBackToWarranty = tool( async () => { return new Command({ update: { currentStep: "warranty_collector" } }); }, { name: "go_back_to_warranty", description: "Go back to warranty verification step.", schema: z.object({}), });const goBackToClassification = tool( async () => { return new Command({ update: { currentStep: "issue_classifier" } }); }, { name: "go_back_to_classification", description: "Go back to issue classification step.", schema: z.object({}), });// Update the resolution_specialist configuration to include these toolsSTEP_CONFIG.resolution_specialist.tools.push( goBackToWarranty, goBackToClassification);
更新解决专家的提示以提及这些工具:
const RESOLUTION_SPECIALIST_PROMPT = `You are a customer support agent helping with device issues.CURRENT STAGE: ResolutionCUSTOMER INFO: Warranty status is {warrantyStatus}, issue type is {issueType}At this step, you need to:1. For SOFTWARE issues: provide troubleshooting steps using provide_solution2. For HARDWARE issues: - If IN WARRANTY: explain warranty repair process using provide_solution - If OUT OF WARRANTY: escalate_to_human for paid repair optionsIf the customer indicates any information was wrong, use:- go_back_to_warranty to correct warranty status- go_back_to_classification to correct issue typeBe 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
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 stepsconst 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 workflowconst 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 referenceconst WARRANTY_COLLECTOR_PROMPT = `You are a customer support agent helping with device issues.CURRENT STAGE: Warranty verificationAt this step, you need to:1. Greet the customer warmly2. Ask if their device is under warranty3. Use record_warranty_status to record their response and move to the next stepBe 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 classificationCUSTOMER INFO: Warranty status is {warranty_status}At this step, you need to:1. Ask the customer to describe their issue2. 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 stepIf unclear, ask clarifying questions before classifying.`;const RESOLUTION_SPECIALIST_PROMPT = `You are a customer support agent helping with device issues.CURRENT STAGE: ResolutionCUSTOMER 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_solution2. For HARDWARE issues: - If IN WARRANTY: explain warranty repair process using provide_solution - If OUT OF WARRANTY: escalate_to_human for paid repair optionsBe 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 configurationsconst allTools = [ recordWarrantyStatus, recordIssueType, provideSolution, escalateToHuman,];const model = new ChatOpenAI({ model: "gpt-5.4-mini",});// Create the agent with step-based configurationconst agent = createAgent({ model, tools: allTools, middleware: [applyStepMiddleware], checkpointer: new MemorySaver(),});// Configuration for this conversation threadconst threadId = uuidv4();const config = { configurable: { thread_id: threadId } };// Turn 1: Initial message - starts with warranty_collector stepconsole.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 warrantyconsole.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 issueconsole.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: Resolutionconsole.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);}