跳到主要内容

⚙️ 配置管理

引言

配置管理是构建灵活、可维护的 LangGraph 应用的关键要素。通过合理的配置管理,你可以在不修改代码的情况下调整应用行为,支持不同的运行环境,并实现动态的参数调整。本节将详细介绍 LangGraph 中的配置管理机制,包括运行时配置、环境变量管理、模型切换等核心概念。

核心概念

RunnableConfig

RunnableConfig 是 LangGraph 中配置管理的核心接口,它允许你在运行时传递各种配置参数来控制图的执行行为。

interface RunnableConfig {
// 可配置参数
configurable?: Record<string, any>;
// 标签,用于标识和过滤
tags?: string[];
// 元数据
metadata?: Record<string, any>;
// 递归限制
recursionLimit?: number;
// 其他配置选项...
}

配置的作用域

配置在 LangGraph 中有不同的作用域:

  • 全局配置:影响整个图的执行
  • 节点配置:只影响特定节点的行为
  • 运行时配置:在每次调用时动态传递

基础配置使用

基本配置示例

基础配置使用
/**
* ============================================================================
* 基础配置 - Basic Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示 LangGraph 的基础配置功能。通过 RunnableConfig 对象,在运行时动态
* 配置模型参数、元数据、标签等,无需修改代码即可调整图的行为。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 可配置参数:通过 configurable 字段传递自定义参数
* 2️⃣ 默认配置:节点可以设置默认参数值
* 3️⃣ 运行时覆盖:invoke 时传入配置覆盖默认值
* 4️⃣ 元数据支持:添加用户ID、会话ID等元数据用于追踪
* 5️⃣ 标签系统:为执行添加标签,便于分类和过滤
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • RunnableConfig 对象:LangGraph 的配置传递机制
* • config.configurable:存储自定义配置参数
* • config.metadata:存储元数据信息
* • config.tags:存储标签数组
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/basic-config.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • configurable 对象中的参数需要在节点中手动读取
* • 配置会在整个图执行过程中传递
* • recursionLimit 可以限制图的最大递归深度
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
model: Annotation<string>(),
temperature: Annotation<number>(),
});

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
// 从配置中获取模型参数
const modelName = config?.configurable?.model || 'gpt-3.5-turbo';
const temperature = config?.configurable?.temperature || 0.7;

console.log(`使用模型: ${modelName}, 温度: ${temperature}`);

// 创建模型实例
const model = new ChatOpenAI({
model: modelName,
temperature: temperature,
});

const lastMessage = state.messages[state.messages.length - 1];
const response = `回复: ${lastMessage} (模型: ${modelName})`;

return {
messages: [response],
model: modelName,
temperature: temperature,
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('chat', chatNode)
.addEdge(START, 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 使用示例
async function runBasicConfig() {
console.log('=== 基础配置使用示例 ===\n');

// 1. 使用默认配置
console.log('1. 使用默认配置:');
const result1 = await app.invoke({
messages: ['你好,世界!'],
});
console.log('结果:', result1);
console.log();

// 2. 使用自定义配置
console.log('2. 使用自定义配置:');
const config: RunnableConfig = {
configurable: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.3,
},
tags: ['production', 'low-temperature'],
metadata: {
user_id: 'user123',
session_id: 'session456',
},
};

const result2 = await app.invoke(
{
messages: ['请解释什么是人工智能'],
},
config
);
console.log('结果:', result2);
console.log();

// 3. 运行时动态配置
console.log('3. 运行时动态配置:');
const dynamicConfig: RunnableConfig = {
configurable: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.9,
},
recursionLimit: 10,
};

const result3 = await app.invoke(
{
messages: ['写一首关于春天的诗'],
},
dynamicConfig
);
console.log('结果:', result3);
}

// 运行示例
if (require.main === module) {
runBasicConfig().catch(console.error);
}

export { app, runBasicConfig };

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 基础配置使用示例 ===

1. 使用默认配置:
使用模型: gpt-3.5-turbo, 温度: 0.7
结果: {
messages: [ '你好,世界!', '回复: 你好,世界! (模型: gpt-3.5-turbo)' ],
model: 'gpt-3.5-turbo',
temperature: 0.7
}

2. 使用自定义配置:
使用模型: qwen-plus, 温度: 0.3
结果: {
messages: [ '请解释什么是人工智能', '回复: 请解释什么是人工智能 (模型: qwen-plus)' ],
model: 'qwen-plus',
temperature: 0.3
}

3. 运行时动态配置:
使用模型: qwen-plus, 温度: 0.9
结果: {
messages: [ '写一首关于春天的诗', '回复: 写一首关于春天的诗 (模型: qwen-plus)' ],
model: 'qwen-plus',
temperature: 0.9
}
*/

环境变量管理

环境变量配置
/**
* ============================================================================
* 环境变量配置 - Environment Variables Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何从环境变量加载配置。使用 dotenv 加载 .env 文件,提供环境配置验证、
* 运行时覆盖、单例模式配置管理器等功能,实现安全的配置管理。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 环境变量加载:从 .env 文件和系统环境变量加载配置
* 2️⃣ 配置验证:验证必需的环境变量是否存在和有效
* 3️⃣ 类型转换:自动将字符串转换为适当的类型(数字、布尔等)
* 4️⃣ 运行时覆盖:支持运行时配置覆盖环境变量
* 5️⃣ 单例管理器:ConfigManager 使用单例模式确保配置一致性
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • dotenv:加载 .env 文件到 process.env
* • loadEnvironmentConfig:从 process.env 提取配置
* • validateEnvironmentConfig:验证配置有效性
* • ConfigManager:单例模式的配置管理器
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/env-config.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • .env 文件不应提交到版本控制
* • 敏感信息(API密钥)应该通过环境变量传递
* • 不同环境使用不同的 .env 文件
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';
import * as dotenv from 'dotenv';

// 加载环境变量
dotenv.config();

// 环境配置接口
interface EnvironmentConfig {
openaiApiKey: string;
defaultModel: string;
defaultTemperature: number;
maxTokens: number;
environment: 'development' | 'staging' | 'production';
}

// 从环境变量加载配置
function loadEnvironmentConfig(): EnvironmentConfig {
return {
openaiApiKey: process.env.OPENAI_API_KEY || '',
defaultModel: process.env.DEFAULT_MODEL || 'gpt-3.5-turbo',
defaultTemperature: parseFloat(process.env.DEFAULT_TEMPERATURE || '0.7'),
maxTokens: parseInt(process.env.MAX_TOKENS || '1000'),
environment: (process.env.NODE_ENV as any) || 'development',
};
}

// 验证环境配置
function validateEnvironmentConfig(config: EnvironmentConfig): void {
if (!config.openaiApiKey) {
throw new Error('OPENAI_API_KEY 环境变量未设置');
}

if (config.defaultTemperature < 0 || config.defaultTemperature > 1) {
throw new Error('DEFAULT_TEMPERATURE 必须在 0-1 之间');
}

if (config.maxTokens <= 0) {
throw new Error('MAX_TOKENS 必须大于 0');
}
}

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
config: Annotation<EnvironmentConfig>(),
});

// 配置节点
function configNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const envConfig = loadEnvironmentConfig();
validateEnvironmentConfig(envConfig);

console.log('环境配置:', {
environment: envConfig.environment,
model: envConfig.defaultModel,
temperature: envConfig.defaultTemperature,
maxTokens: envConfig.maxTokens,
});

return {
config: envConfig,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const envConfig = state.config;

// 运行时配置可以覆盖环境配置
const modelName = config?.configurable?.model || envConfig.defaultModel;
const temperature =
config?.configurable?.temperature || envConfig.defaultTemperature;
const maxTokens = config?.configurable?.maxTokens || envConfig.maxTokens;

console.log(
`使用配置 - 模型: ${modelName}, 温度: ${temperature}, 最大令牌: ${maxTokens}`
);

const model = new ChatOpenAI({
apiKey: envConfig.openaiApiKey,
model: modelName,
temperature: temperature,
maxTokens: maxTokens,
});

const lastMessage = state.messages[state.messages.length - 1];
const response = `环境: ${envConfig.environment} | 回复: ${lastMessage}`;

return {
messages: [response],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('config_node', configNode)
.addNode('chat', chatNode)
.addEdge(START, 'config_node')
.addEdge('config_node', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 使用示例
async function runEnvironmentConfig() {
console.log('=== 环境变量配置示例 ===\n');

try {
// 1. 使用环境变量配置
console.log('1. 使用环境变量配置:');
const result1 = await app.invoke({
messages: ['你好,请介绍一下你自己'],
});
console.log('结果:', result1);
console.log();

// 2. 运行时覆盖环境配置
console.log('2. 运行时覆盖环境配置:');
const overrideConfig: RunnableConfig = {
configurable: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.2,
maxTokens: 500,
},
};

const result2 = await app.invoke(
{
messages: ['请用简洁的语言解释量子计算'],
},
overrideConfig
);
console.log('结果:', result2);
console.log();

// 3. 根据环境调整行为
console.log('3. 根据环境调整行为:');
const envConfig = loadEnvironmentConfig();

const environmentSpecificConfig: RunnableConfig = {
configurable: {
// 生产环境使用更保守的参数
temperature: envConfig.environment === 'production' ? 0.3 : 0.8,
maxTokens: envConfig.environment === 'production' ? 800 : 1200,
},
tags: [envConfig.environment],
metadata: {
environment: envConfig.environment,
timestamp: new Date().toISOString(),
},
};

const result3 = await app.invoke(
{
messages: ['创作一个有趣的故事'],
},
environmentSpecificConfig
);
console.log('结果:', result3);
} catch (error) {
console.error('配置错误:', error.message);
}
}

// 配置管理工具类
class ConfigManager {
private static instance: ConfigManager;
private config: EnvironmentConfig;

private constructor() {
this.config = loadEnvironmentConfig();
validateEnvironmentConfig(this.config);
}

static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}

getConfig(): EnvironmentConfig {
return { ...this.config };
}

updateConfig(updates: Partial<EnvironmentConfig>): void {
this.config = { ...this.config, ...updates };
validateEnvironmentConfig(this.config);
}

getRunnableConfig(overrides?: Record<string, any>): RunnableConfig {
return {
configurable: {
model: this.config.defaultModel,
temperature: this.config.defaultTemperature,
maxTokens: this.config.maxTokens,
...overrides,
},
tags: [this.config.environment],
metadata: {
environment: this.config.environment,
configVersion: '1.0.0',
},
};
}
}

// 运行示例
if (require.main === module) {
runEnvironmentConfig().catch(console.error);
}

export { app, runEnvironmentConfig, ConfigManager, loadEnvironmentConfig };

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 环境变量配置示例 ===

1. 使用环境变量配置:
环境配置: {
environment: 'development',
model: 'gpt-3.5-turbo',
temperature: 0.7,
maxTokens: 1000
}
使用配置 - 模型: gpt-3.5-turbo, 温度: 0.7, 最大令牌: 1000
结果: {
messages: [ '你好,请介绍一下你自己', '环境: development | 回复: 你好,请介绍一下你自己' ],
config: {
openaiApiKey: 'sk-cd7189c3f7f74c018b59ee86d2e78a06',
defaultModel: 'gpt-3.5-turbo',
defaultTemperature: 0.7,
maxTokens: 1000,
environment: 'development'
}
}

2. 运行时覆盖环境配置:
环境配置: {
environment: 'development',
model: 'gpt-3.5-turbo',
temperature: 0.7,
maxTokens: 1000
}
使用配置 - 模型: qwen-plus, 温度: 0.2, 最大令牌: 500
结果: {
messages: [ '请用简洁的语言解释量子计算', '环境: development | 回复: 请用简洁的语言解释量子计算' ],
config: {
openaiApiKey: 'sk-cd7189c3f7f74c018b59ee86d2e78a06',
defaultModel: 'gpt-3.5-turbo',
defaultTemperature: 0.7,
maxTokens: 1000,
environment: 'development'
}
}

3. 根据环境调整行为:
环境配置: {
environment: 'development',
model: 'gpt-3.5-turbo',
temperature: 0.7,
maxTokens: 1000
}
使用配置 - 模型: gpt-3.5-turbo, 温度: 0.8, 最大令牌: 1200
结果: {
messages: [ '创作一个有趣的故事', '环境: development | 回复: 创作一个有趣的故事' ],
config: {
openaiApiKey: 'sk-cd7189c3f7f74c018b59ee86d2e78a06',
defaultModel: 'gpt-3.5-turbo',
defaultTemperature: 0.7,
maxTokens: 1000,
environment: 'development'
}
}
*/

高级配置模式

模型切换配置

通过配置管理,你可以轻松地在不同的 LLM 模型之间切换:

模型切换配置
/**
* ============================================================================
* 模型切换配置 - Model Switching Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何在 LangGraph 中实现动态模型切换。支持多个模型提供商(OpenAI、
* Anthropic),提供模型对比、自动故障转移、备用模型等功能。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 多模型支持:支持 GPT-3.5、GPT-4、Claude 等多种模型
* 2️⃣ 运行时切换:通过配置动态选择不同的模型
* 3️⃣ 模型对比:并行测试多个模型,对比输出效果
* 4️⃣ 故障转移:主模型失败时自动切换到备用模型
* 5️⃣ 模型工厂:统一的模型创建接口支持多个提供商
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • MODEL_CONFIGS:预定义的模型配置映射
* • createModel 工厂函数:根据提供商创建对应的模型实例
* • ModelSwitcher:智能模型切换器,支持故障转移
* • compareModels:批量测试多个模型并对比结果
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/model-switching.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 不同模型的API调用成本不同
* • 模型切换可能影响输出一致性
* • 建议设置合理的超时和重试策略
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 模型配置接口
interface ModelConfig {
provider: 'openai' | 'anthropic' | 'local';
model: string;
temperature: number;
maxTokens: number;
}

// 预定义的模型配置
const MODEL_CONFIGS: Record<string, ModelConfig> = {
'gpt-3.5-turbo': {
provider: 'openai',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
maxTokens: 1000,
},
'gpt-4': {
provider: 'openai',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
maxTokens: 2000,
},
'claude-3-sonnet': {
provider: 'anthropic',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
maxTokens: 1500,
},
'claude-3-haiku': {
provider: 'anthropic',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
maxTokens: 1000,
},
};

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
modelConfig: Annotation<ModelConfig>(),
responses: Annotation<Record<string, string>>({
reducer: (
state: Record<string, string>,
update: Record<string, string>
) => ({
...state,
...update,
}),
default: () => ({}),
}),
});

// 模型工厂函数
function createModel(config: ModelConfig) {
switch (config.provider) {
case 'openai':
return new ChatOpenAI({
model: config.model,
temperature: config.temperature,
maxTokens: config.maxTokens,
});
case 'anthropic':
// 注意:实际使用时需要安装 @langchain/anthropic 包
// 这里使用模拟实现
console.log(`模拟 Anthropic 模型: ${config.model}`);
return new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME, // 用 OpenAI 模型模拟
temperature: config.temperature,
maxTokens: config.maxTokens,
});
case 'local':
throw new Error('本地模型暂未实现');
default:
throw new Error(`不支持的模型提供商: ${config.provider}`);
}
}

// 模型选择节点
function modelSelectionNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const modelKey = config?.configurable?.model || 'gpt-3.5-turbo';
const modelConfig = MODEL_CONFIGS[modelKey];

if (!modelConfig) {
throw new Error(`未找到模型配置: ${modelKey}`);
}

// 允许运行时覆盖配置
const finalConfig: ModelConfig = {
...modelConfig,
temperature: config?.configurable?.temperature ?? modelConfig.temperature,
maxTokens: config?.configurable?.maxTokens ?? modelConfig.maxTokens,
};

console.log(`选择模型: ${finalConfig.model} (${finalConfig.provider})`);

return {
modelConfig: finalConfig,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { modelConfig } = state;
const model = createModel(modelConfig);

const lastMessage = state.messages[state.messages.length - 1];
console.log(`使用 ${modelConfig.model} 处理消息: ${lastMessage}`);

// 模拟模型响应
const response = `[${modelConfig.model}] 回复: ${lastMessage}`;

return {
messages: [response],
responses: {
[modelConfig.model]: response,
},
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('selectModel', modelSelectionNode)
.addNode('chat', chatNode)
.addEdge(START, 'selectModel')
.addEdge('selectModel', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 模型比较功能
async function compareModels(message: string, modelKeys: string[]) {
console.log(`=== 模型比较: "${message}" ===\n`);

const results: Record<string, any> = {};

for (const modelKey of modelKeys) {
console.log(`测试模型: ${modelKey}`);

const config: RunnableConfig = {
configurable: {
model: modelKey,
},
};

try {
const result = await app.invoke(
{
messages: [message],
},
config
);

results[modelKey] = result;
console.log(`结果: ${result.messages[result.messages.length - 1]}\n`);
} catch (error) {
console.error(`模型 ${modelKey} 执行失败:`, error.message);
results[modelKey] = { error: error.message };
}
}

return results;
}

// 动态模型切换
class ModelSwitcher {
private currentModel: string = 'gpt-3.5-turbo';
private fallbackModels: string[] = ['gpt-3.5-turbo', 'claude-3-haiku'];

async invoke(message: string, preferredModel?: string): Promise<any> {
const modelToUse = preferredModel || this.currentModel;

const config: RunnableConfig = {
configurable: {
model: modelToUse,
},
};

try {
const result = await app.invoke(
{
messages: [message],
},
config
);

// 成功后更新当前模型
this.currentModel = modelToUse;
return result;
} catch (error) {
console.warn(`模型 ${modelToUse} 失败,尝试备用模型`);

// 尝试备用模型
for (const fallbackModel of this.fallbackModels) {
if (fallbackModel === modelToUse) continue;

try {
const fallbackConfig: RunnableConfig = {
configurable: {
model: fallbackModel,
},
};

const result = await app.invoke(
{
messages: [message],
},
fallbackConfig
);

console.log(`使用备用模型 ${fallbackModel} 成功`);
this.currentModel = fallbackModel;
return result;
} catch (fallbackError) {
console.warn(`备用模型 ${fallbackModel} 也失败了`);
}
}

throw new Error('所有模型都失败了');
}
}

setFallbackModels(models: string[]): void {
this.fallbackModels = models;
}

getCurrentModel(): string {
return this.currentModel;
}
}

// 使用示例
async function runModelSwitching() {
console.log('=== 模型切换配置示例 ===\n');

// 1. 基本模型切换
console.log('1. 基本模型切换:');

const models = ['gpt-3.5-turbo', 'gpt-4', 'claude-3-haiku'];
for (const model of models) {
const config: RunnableConfig = {
configurable: {
model: model,
temperature: 0.5,
},
};

const result = await app.invoke(
{
messages: ['解释什么是机器学习'],
},
config
);

console.log(`${model}:`, result.messages[result.messages.length - 1]);
}
console.log();

// 2. 模型比较
console.log('2. 模型比较:');
await compareModels('写一首关于人工智能的诗', [
'gpt-3.5-turbo',
'claude-3-haiku',
]);

// 3. 动态模型切换
console.log('3. 动态模型切换:');
const switcher = new ModelSwitcher();

const result1 = await switcher.invoke('你好,世界!', 'gpt-4');
console.log('首选模型结果:', result1.messages[result1.messages.length - 1]);

const result2 = await switcher.invoke('再次问好');
console.log('当前模型结果:', result2.messages[result2.messages.length - 1]);

console.log('当前使用的模型:', switcher.getCurrentModel());
}

// 运行示例
if (require.main === module) {
runModelSwitching().catch(console.error);
}

export { app, runModelSwitching, compareModels, ModelSwitcher, MODEL_CONFIGS };

/*执行结果: 模型切换演示,包括gpt-3.5/gpt-4/claude切换,模型比较,动态切换*/

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 模型切换配置示例 ===

1. 基本模型切换:
选择模型: qwen-plus (openai)
使用 qwen-plus 处理消息: 解释什么是机器学习
gpt-3.5-turbo: [qwen-plus] 回复: 解释什么是机器学习
选择模型: qwen-plus (openai)
使用 qwen-plus 处理消息: 解释什么是机器学习
gpt-4: [qwen-plus] 回复: 解释什么是机器学习
选择模型: qwen-plus (anthropic)
模拟 Anthropic 模型: qwen-plus
使用 qwen-plus 处理消息: 解释什么是机器学习
claude-3-haiku: [qwen-plus] 回复: 解释什么是机器学习

2. 模型比较:
=== 模型比较: "写一首关于人工智能的诗" ===

测试模型: gpt-3.5-turbo
选择模型: qwen-plus (openai)
使用 qwen-plus 处理消息: 写一首关于人工智能的诗
结果: [qwen-plus] 回复: 写一首关于人工智能的诗

测试模型: claude-3-haiku
选择模型: qwen-plus (anthropic)
模拟 Anthropic 模型: qwen-plus
使用 qwen-plus 处理消息: 写一首关于人工智能的诗
结果: [qwen-plus] 回复: 写一首关于人工智能的诗

3. 动态模型切换:
选择模型: qwen-plus (openai)
使用 qwen-plus 处理消息: 你好,世界!
首选模型结果: [qwen-plus] 回复: 你好,世界!
选择模型: qwen-plus (openai)
使用 qwen-plus 处理消息: 再次问好
当前模型结果: [qwen-plus] 回复: 再次问好
当前使用的模型: gpt-4
*/

动态参数调整

动态参数配置
/**
* ============================================================================
* 动态参数配置 - Dynamic Parameters Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何动态调整模型参数。提供预设配置模板(创意、分析、平衡、结构化),
* 支持根据消息类型、用户偏好、上下文自动调整参数,实现智能化参数配置。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 预设模板:提供 creative、analytical、balanced、structured 四种预设
* 2️⃣ 运行时覆盖:可在运行时覆盖预设的任意参数
* 3️⃣ 上下文感知:根据消息类型自动调整参数(创意、分析、随意、正式)
* 4️⃣ 用户偏好:根据用户偏好设置个性化参数
* 5️⃣ 自适应调整:根据对话长度、回复质量、满意度动态调整
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • PRESET_CONFIGS:预定义的参数配置模板
* • DynamicParameterAdjuster:智能参数调整器
* • 参数合并策略:预设 + 覆盖 + 上下文调整
* • 响应格式支持:text 和 json 两种输出格式
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/dynamic-params.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 参数调整应该有上下限保护
* • 建议记录参数调整历史用于分析
* • 不同模型对参数敏感度不同,需分别调优
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 动态参数配置接口
interface DynamicParams {
temperature: number;
maxTokens: number;
topP: number;
frequencyPenalty: number;
presencePenalty: number;
systemPrompt: string;
responseFormat: 'text' | 'json';
}

// 预设配置模板
const PRESET_CONFIGS: Record<string, Partial<DynamicParams>> = {
creative: {
temperature: 0.9,
maxTokens: 2000,
topP: 0.95,
frequencyPenalty: 0.5,
presencePenalty: 0.3,
systemPrompt: '你是一个富有创造力的助手,善于产生新颖的想法和创意内容。',
responseFormat: 'text',
},
analytical: {
temperature: 0.2,
maxTokens: 1500,
topP: 0.8,
frequencyPenalty: 0.0,
presencePenalty: 0.0,
systemPrompt:
'你是一个逻辑严谨的分析师,专注于提供准确、客观的分析和解答。',
responseFormat: 'text',
},
balanced: {
temperature: 0.7,
maxTokens: 1000,
topP: 0.9,
frequencyPenalty: 0.2,
presencePenalty: 0.1,
systemPrompt: '你是一个平衡的助手,既有创造力又保持准确性。',
responseFormat: 'text',
},
structured: {
temperature: 0.3,
maxTokens: 800,
topP: 0.85,
frequencyPenalty: 0.0,
presencePenalty: 0.0,
systemPrompt: '请以结构化的方式回答,使用清晰的格式和组织。',
responseFormat: 'json',
},
};

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
params: Annotation<DynamicParams>(),
preset: Annotation<string>(),
});

// 参数配置节点
function configureParamsNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
// 获取预设配置
const presetName = config?.configurable?.preset || 'balanced';
const presetConfig = PRESET_CONFIGS[presetName] || PRESET_CONFIGS.balanced;

// 合并运行时参数覆盖
const finalParams: DynamicParams = {
temperature:
config?.configurable?.temperature ?? presetConfig.temperature ?? 0.7,
maxTokens:
config?.configurable?.maxTokens ?? presetConfig.maxTokens ?? 1000,
topP: config?.configurable?.topP ?? presetConfig.topP ?? 0.9,
frequencyPenalty:
config?.configurable?.frequencyPenalty ??
presetConfig.frequencyPenalty ??
0.0,
presencePenalty:
config?.configurable?.presencePenalty ??
presetConfig.presencePenalty ??
0.0,
systemPrompt:
config?.configurable?.systemPrompt ??
presetConfig.systemPrompt ??
'你是一个有用的助手。',
responseFormat:
config?.configurable?.responseFormat ??
presetConfig.responseFormat ??
'text',
};

console.log(`使用预设: ${presetName}`);
console.log('最终参数:', finalParams);

return {
params: finalParams,
preset: presetName,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { params } = state;

// 创建模型实例
const model = new ChatOpenAI({
model: process.env.OPENAI_MODEL_NAME,
temperature: params.temperature,
maxTokens: params.maxTokens,
topP: params.topP,
frequencyPenalty: params.frequencyPenalty,
presencePenalty: params.presencePenalty,
});

const lastMessage = state.messages[state.messages.length - 1];

// 构建完整的提示词
const fullPrompt = `${params.systemPrompt}\n\n用户问题: ${lastMessage}`;

console.log(`处理消息: ${lastMessage}`);
console.log(`系统提示: ${params.systemPrompt}`);

// 模拟响应(实际应用中会调用模型)
let response = `[${state.preset}模式] 回复: ${lastMessage}`;

// 根据响应格式调整输出
if (params.responseFormat === 'json') {
response = JSON.stringify(
{
mode: state.preset,
response: `回复: ${lastMessage}`,
parameters: params,
},
null,
2
);
}

return {
messages: [response],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('configure', configureParamsNode)
.addNode('chat', chatNode)
.addEdge(START, 'configure')
.addEdge('configure', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 动态参数调整器
class DynamicParameterAdjuster {
private baseConfig: Partial<DynamicParams>;

constructor(baseConfig: Partial<DynamicParams> = {}) {
this.baseConfig = baseConfig;
}

// 根据消息类型调整参数
adjustForMessageType(
messageType: 'creative' | 'analytical' | 'casual' | 'formal'
): RunnableConfig {
let adjustments: Partial<DynamicParams> = {};

switch (messageType) {
case 'creative':
adjustments = {
temperature: 0.9,
topP: 0.95,
frequencyPenalty: 0.5,
systemPrompt: '发挥你的创造力,提供独特和有趣的回答。',
};
break;
case 'analytical':
adjustments = {
temperature: 0.2,
topP: 0.8,
frequencyPenalty: 0.0,
systemPrompt: '请提供详细、准确的分析和解释。',
};
break;
case 'casual':
adjustments = {
temperature: 0.8,
systemPrompt: '用轻松、友好的语调回答。',
};
break;
case 'formal':
adjustments = {
temperature: 0.3,
systemPrompt: '请使用正式、专业的语言回答。',
};
break;
}

return {
configurable: {
...this.baseConfig,
...adjustments,
},
};
}

// 根据用户偏好调整参数
adjustForUserPreference(
userId: string,
preferences: Record<string, any>
): RunnableConfig {
const userAdjustments: Partial<DynamicParams> = {
temperature: preferences.creativity || 0.7,
maxTokens: preferences.responseLength || 1000,
systemPrompt: preferences.tone
? `请使用${preferences.tone}的语调回答。`
: '你是一个有用的助手。',
};

return {
configurable: {
...this.baseConfig,
...userAdjustments,
},
metadata: {
userId,
preferences,
},
};
}

// 根据上下文动态调整
adjustForContext(context: {
conversationLength: number;
lastResponseQuality: 'good' | 'poor';
userSatisfaction: number; // 0-1
}): RunnableConfig {
let adjustments: Partial<DynamicParams> = {};

// 根据对话长度调整
if (context.conversationLength > 10) {
adjustments.temperature = Math.max(
0.3,
adjustments.temperature || 0.7 - 0.1
);
adjustments.maxTokens = 800; // 长对话使用较短回复
}

// 根据上次回复质量调整
if (context.lastResponseQuality === 'poor') {
adjustments.temperature = 0.5; // 降低随机性
adjustments.systemPrompt = '请提供更准确、更有帮助的回答。';
}

// 根据用户满意度调整
if (context.userSatisfaction < 0.5) {
adjustments.temperature = 0.4;
adjustments.systemPrompt = '请特别注意提供高质量、有用的回答。';
}

return {
configurable: {
...this.baseConfig,
...adjustments,
},
metadata: {
context,
},
};
}
}

// 使用示例
async function runDynamicParams() {
console.log('=== 动态参数配置示例 ===\n');

// 1. 使用预设配置
console.log('1. 使用预设配置:');

const presets = ['creative', 'analytical', 'balanced', 'structured'];
for (const preset of presets) {
const config: RunnableConfig = {
configurable: {
preset: preset,
},
};

const result = await app.invoke(
{
messages: ['解释什么是人工智能'],
},
config
);

console.log(`${preset}:`, result.messages[result.messages.length - 1]);
console.log();
}

// 2. 运行时参数覆盖
console.log('2. 运行时参数覆盖:');
const customConfig: RunnableConfig = {
configurable: {
preset: 'creative',
temperature: 0.5, // 覆盖预设的温度
systemPrompt: '你是一个专业的技术顾问。', // 覆盖预设的系统提示
responseFormat: 'json',
},
};

const result2 = await app.invoke(
{
messages: ['推荐一些学习编程的方法'],
},
customConfig
);

console.log('自定义配置结果:', result2.messages[result2.messages.length - 1]);
console.log();

// 3. 动态参数调整
console.log('3. 动态参数调整:');
const adjuster = new DynamicParameterAdjuster();

// 根据消息类型调整
const creativeConfig = adjuster.adjustForMessageType('creative');
const result3 = await app.invoke(
{
messages: ['写一首关于编程的诗'],
},
creativeConfig
);

console.log('创意模式:', result3.messages[result3.messages.length - 1]);

// 根据用户偏好调整
const userConfig = adjuster.adjustForUserPreference('user123', {
creativity: 0.8,
responseLength: 1200,
tone: '友好',
});

const result4 = await app.invoke(
{
messages: ['介绍一下 JavaScript'],
},
userConfig
);

console.log('用户偏好模式:', result4.messages[result4.messages.length - 1]);

// 根据上下文调整
const contextConfig = adjuster.adjustForContext({
conversationLength: 15,
lastResponseQuality: 'poor',
userSatisfaction: 0.3,
});

const result5 = await app.invoke(
{
messages: ['请再详细解释一下'],
},
contextConfig
);

console.log('上下文调整模式:', result5.messages[result5.messages.length - 1]);
}

// 运行示例
if (require.main === module) {
runDynamicParams().catch(console.error);
}

export { app, runDynamicParams, DynamicParameterAdjuster, PRESET_CONFIGS };

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 动态参数配置示例 ===

1. 使用预设配置:
使用预设: creative
最终参数: {
temperature: 0.9,
maxTokens: 2000,
topP: 0.95,
frequencyPenalty: 0.5,
presencePenalty: 0.3,
systemPrompt: '你是一个富有创造力的助手,善于产生新颖的想法和创意内容。',
responseFormat: 'text'
}
处理消息: 解释什么是人工智能
系统提示: 你是一个富有创造力的助手,善于产生新颖的想法和创意内容。
creative: [creative模式] 回复: 解释什么是人工智能

使用预设: analytical
最终参数: {
temperature: 0.2,
maxTokens: 1500,
topP: 0.8,
frequencyPenalty: 0,
presencePenalty: 0,
systemPrompt: '你是一个逻辑严谨的分析师,专注于提供准确、客观的分析和解答。',
responseFormat: 'text'
}
处理消息: 解释什么是人工智能
系统提示: 你是一个逻辑严谨的分析师,专注于提供准确、客观的分析和解答。
analytical: [analytical模式] 回复: 解释什么是人工智能

使用预设: balanced
最终参数: {
temperature: 0.7,
maxTokens: 1000,
topP: 0.9,
frequencyPenalty: 0.2,
presencePenalty: 0.1,
systemPrompt: '你是一个平衡的助手,既有创造力又保持准确性。',
responseFormat: 'text'
}
处理消息: 解释什么是人工智能
系统提示: 你是一个平衡的助手,既有创造力又保持准确性。
balanced: [balanced模式] 回复: 解释什么是人工智能

使用预设: structured
最终参数: {
temperature: 0.3,
maxTokens: 800,
topP: 0.85,
frequencyPenalty: 0,
presencePenalty: 0,
systemPrompt: '请以结构化的方式回答,使用清晰的格式和组织。',
responseFormat: 'json'
}
处理消息: 解释什么是人工智能
系统提示: 请以结构化的方式回答,使用清晰的格式和组织。
structured: {
"mode": "structured",
"response": "回复: 解释什么是人工智能",
"parameters": {
"temperature": 0.3,
"maxTokens": 800,
"topP": 0.85,
"frequencyPenalty": 0,
"presencePenalty": 0,
"systemPrompt": "请以结构化的方式回答,使用清晰的格式和组织。",
"responseFormat": "json"
}
}

2. 运行时参数覆盖:
使用预设: creative
最终参数: {
temperature: 0.5,
maxTokens: 2000,
topP: 0.95,
frequencyPenalty: 0.5,
presencePenalty: 0.3,
systemPrompt: '你是一个专业的技术顾问。',
responseFormat: 'json'
}
处理消息: 推荐一些学习编程的方法
系统提示: 你是一个专业的技术顾问。
自定义配置结果: {
"mode": "creative",
"response": "回复: 推荐一些学习编程的方法",
"parameters": {
"temperature": 0.5,
"maxTokens": 2000,
"topP": 0.95,
"frequencyPenalty": 0.5,
"presencePenalty": 0.3,
"systemPrompt": "你是一个专业的技术顾问。",
"responseFormat": "json"
}
}

3. 动态参数调整:
使用预设: balanced
最终参数: {
temperature: 0.9,
maxTokens: 1000,
topP: 0.95,
frequencyPenalty: 0.5,
presencePenalty: 0.1,
systemPrompt: '发挥你的创造力,提供独特和有趣的回答。',
responseFormat: 'text'
}
处理消息: 写一首关于编程的诗
系统提示: 发挥你的创造力,提供独特和有趣的回答。
创意模式: [balanced模式] 回复: 写一首关于编程的诗
使用预设: balanced
最终参数: {
temperature: 0.8,
maxTokens: 1200,
topP: 0.9,
frequencyPenalty: 0.2,
presencePenalty: 0.1,
systemPrompt: '请使用友好的语调回答。',
responseFormat: 'text'
}
处理消息: 介绍一下 JavaScript
系统提示: 请使用友好的语调回答。
用户偏好模式: [balanced模式] 回复: 介绍一下 JavaScript
使用预设: balanced
最终参数: {
temperature: 0.4,
maxTokens: 800,
topP: 0.9,
frequencyPenalty: 0.2,
presencePenalty: 0.1,
systemPrompt: '请特别注意提供高质量、有用的回答。',
responseFormat: 'text'
}
处理消息: 请再详细解释一下
系统提示: 请特别注意提供高质量、有用的回答。
上下文调整模式: [balanced模式] 回复: 请再详细解释一下
*/

配置继承和覆盖

配置继承机制

LangGraph 支持配置的继承和覆盖机制,子图会继承父图的配置:

配置优先级

配置的优先级从高到低为:

  1. 运行时传递的配置
  2. 节点级别的配置
  3. 图级别的配置
  4. 默认配置
配置优先级示例
/**
* ============================================================================
* 配置优先级 - Configuration Priority
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何处理多层级的配置优先级。支持默认配置、图级配置、节点级配置和运行时
* 配置,遵循优先级规则(运行时 > 节点 > 图 > 默认)进行合并。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 四级配置:默认 < 图级 < 节点级 < 运行时
* 2️⃣ 配置合并:高优先级配置覆盖低优先级配置
* 3️⃣ 配置历史:记录每一层配置的应用过程
* 4️⃣ 配置验证:验证合并后的配置是否有效
* 5️⃣ 配置追溯:可以追踪最终配置来源
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 配置层级系统:定义不同级别的配置对象
* • mergeConfigs 函数:按优先级顺序合并配置
* • ConfigPriorityTester:测试不同优先级场景
* • ConfigValidator:验证最终配置的有效性
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/config-priority.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 配置优先级规则应该清晰文档化
* • 注意部分覆盖与完全覆盖的区别
* • 建议记录配置来源以便调试
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 配置层级接口
interface ConfigLevel {
level: 'default' | 'graph' | 'node' | 'runtime';
temperature: number;
model: string;
systemPrompt: string;
source: string;
}

// 默认配置
const DEFAULT_CONFIG: ConfigLevel = {
level: 'default',
temperature: 0.7,
model: process.env.OPENAI_MODEL_NAME,
systemPrompt: '你是一个有用的助手。',
source: '系统默认配置',
};

// 图级别配置
const GRAPH_CONFIG: Partial<ConfigLevel> = {
level: 'graph',
temperature: 0.5,
systemPrompt: '你是一个专业的技术助手。',
source: '图级别配置',
};

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
finalConfig: Annotation<ConfigLevel>(),
configHistory: Annotation<ConfigLevel[]>({
reducer: (state: ConfigLevel[], update: ConfigLevel[]) =>
state.concat(update),
default: () => [],
}),
});

// 配置合并函数
function mergeConfigs(
...configs: (Partial<ConfigLevel> | undefined)[]
): ConfigLevel {
const validConfigs = configs.filter(Boolean) as Partial<ConfigLevel>[];

// 从默认配置开始,逐层覆盖
let merged: ConfigLevel = { ...DEFAULT_CONFIG };

for (const config of validConfigs) {
if (config.temperature !== undefined)
merged.temperature = config.temperature;
if (config.model !== undefined) merged.model = config.model;
if (config.systemPrompt !== undefined)
merged.systemPrompt = config.systemPrompt;
if (config.level !== undefined) merged.level = config.level;
if (config.source !== undefined) merged.source = config.source;
}

return merged;
}

// 配置解析节点
function configResolutionNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
console.log('=== 配置优先级解析 ===');

// 收集所有配置层级
const configLevels: (Partial<ConfigLevel> | undefined)[] = [
// 1. 默认配置(最低优先级)
DEFAULT_CONFIG,

// 2. 图级别配置
GRAPH_CONFIG,

// 3. 节点级别配置(模拟)
{
level: 'node',
model: process.env.OPENAI_MODEL_NAME,
source: '节点级别配置',
},

// 4. 运行时配置(最高优先级)
config?.configurable
? {
level: 'runtime',
temperature: config.configurable.temperature,
model: config.configurable.model,
systemPrompt: config.configurable.systemPrompt,
source: '运行时配置',
}
: undefined,
];

// 记录配置历史
const history: ConfigLevel[] = [];
let currentConfig = { ...DEFAULT_CONFIG };

for (const levelConfig of configLevels) {
if (levelConfig) {
const beforeMerge = { ...currentConfig };
currentConfig = mergeConfigs(currentConfig, levelConfig);

console.log(`应用 ${levelConfig.source || levelConfig.level}:`);
console.log(
` 温度: ${beforeMerge.temperature} -> ${currentConfig.temperature}`
);
console.log(` 模型: ${beforeMerge.model} -> ${currentConfig.model}`);
console.log(
` 提示: ${beforeMerge.systemPrompt.substring(
0,
20
)}... -> ${currentConfig.systemPrompt.substring(0, 20)}...`
);
console.log();

history.push({ ...currentConfig });
}
}

console.log('最终配置:', currentConfig);
console.log('================\n');

return {
finalConfig: currentConfig,
configHistory: history,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { finalConfig } = state;

// 使用最终解析的配置创建模型
const model = new ChatOpenAI({
model: finalConfig.model,
temperature: finalConfig.temperature,
});

const lastMessage = state.messages[state.messages.length - 1];

console.log(`使用最终配置处理消息:`);
console.log(` 模型: ${finalConfig.model}`);
console.log(` 温度: ${finalConfig.temperature}`);
console.log(` 系统提示: ${finalConfig.systemPrompt}`);
console.log(` 配置来源: ${finalConfig.source}`);

// 模拟响应
const response = `[${finalConfig.model}@${finalConfig.temperature}] ${
finalConfig.systemPrompt.split('。')[0]
}。回复: ${lastMessage}`;

return {
messages: [response],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('resolveConfig', configResolutionNode)
.addNode('chat', chatNode)
.addEdge(START, 'resolveConfig')
.addEdge('resolveConfig', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 配置优先级测试器
class ConfigPriorityTester {
async testPriority(
message: string,
scenarios: Array<{
name: string;
config?: RunnableConfig;
}>
) {
console.log(`=== 配置优先级测试: "${message}" ===\n`);

for (const scenario of scenarios) {
console.log(`场景: ${scenario.name}`);
console.log('运行时配置:', scenario.config?.configurable || '无');
console.log();

const result = await app.invoke({ messages: [message] }, scenario.config);

console.log('结果:', result.messages[result.messages.length - 1]);
console.log('配置历史:');
result.configHistory.forEach((config, index) => {
console.log(
` ${index + 1}. ${config.source}: 温度=${config.temperature}, 模型=${
config.model
}`
);
});
console.log('\n' + '='.repeat(50) + '\n');
}
}

async demonstrateOverrides() {
console.log('=== 配置覆盖演示 ===\n');

const testCases = [
{
name: '仅使用默认配置',
config: undefined,
},
{
name: '运行时覆盖温度',
config: {
configurable: {
temperature: 0.9,
},
},
},
{
name: '运行时覆盖模型',
config: {
configurable: {
model: process.env.OPENAI_MODEL_NAME,
},
},
},
{
name: '运行时覆盖系统提示',
config: {
configurable: {
systemPrompt: '你是一个创意写作助手,专门帮助用户创作有趣的内容。',
},
},
},
{
name: '运行时覆盖所有参数',
config: {
configurable: {
temperature: 0.1,
model: process.env.OPENAI_MODEL_NAME,
systemPrompt:
'你是一个严格的逻辑分析师,只提供基于事实的准确回答。',
},
},
},
];

await this.testPriority('解释什么是配置优先级', testCases);
}
}

// 配置验证器
class ConfigValidator {
validateConfig(config: Partial<ConfigLevel>): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];

if (config.temperature !== undefined) {
if (config.temperature < 0 || config.temperature > 1) {
errors.push('温度必须在 0-1 之间');
}
}

if (config.model !== undefined) {
const validModels = ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo'];
if (!validModels.includes(config.model)) {
errors.push(`模型必须是以下之一: ${validModels.join(', ')}`);
}
}

if (config.systemPrompt !== undefined) {
if (config.systemPrompt.length > 1000) {
errors.push('系统提示不能超过 1000 个字符');
}
}

return {
valid: errors.length === 0,
errors,
};
}

async validateAndInvoke(
message: string,
config?: RunnableConfig
): Promise<any> {
if (config?.configurable) {
const validation = this.validateConfig(config.configurable);
if (!validation.valid) {
throw new Error(`配置验证失败: ${validation.errors.join(', ')}`);
}
}

return await app.invoke({ messages: [message] }, config);
}
}

// 使用示例
async function runConfigPriority() {
console.log('=== 配置优先级示例 ===\n');

// 1. 基本优先级演示
const tester = new ConfigPriorityTester();
await tester.demonstrateOverrides();

// 2. 配置验证
console.log('=== 配置验证示例 ===\n');
const validator = new ConfigValidator();

try {
// 有效配置
const result1 = await validator.validateAndInvoke('你好', {
configurable: {
temperature: 0.8,
model: process.env.OPENAI_MODEL_NAME,
},
});
console.log('有效配置结果:', result1.messages[result1.messages.length - 1]);
} catch (error) {
console.error('配置错误:', error.message);
}

try {
// 无效配置
await validator.validateAndInvoke('你好', {
configurable: {
temperature: 1.5, // 无效温度
model: process.env.OPENAI_MODEL_NAME, // 无效模型
},
});
} catch (error) {
console.error('预期的配置错误:', error.message);
}
}

// 运行示例
if (require.main === module) {
runConfigPriority().catch(console.error);
}

export {
app,
runConfigPriority,
ConfigPriorityTester,
ConfigValidator,
mergeConfigs,
DEFAULT_CONFIG,
GRAPH_CONFIG,
};

/*执行结果: 配置优先级测试通过,演示系统默认、图级别、节点级别、运行时配置的优先级*/

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 配置优先级示例 ===

=== 配置覆盖演示 ===

=== 配置优先级测试: "解释什么是配置优先级" ===

场景: 仅使用默认配置
运行时配置: 无

=== 配置优先级解析 ===
应用 系统默认配置:
温度: 0.7 -> 0.7
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个有用的助手。...

应用 图级别配置:
温度: 0.7 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个专业的技术助手。...

应用 节点级别配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

应用 运行时配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

最终配置: {
level: 'runtime',
temperature: 0.5,
model: 'qwen-plus',
systemPrompt: '你是一个专业的技术助手。',
source: '运行时配置'
}
================

使用最终配置处理消息:
模型: qwen-plus
温度: 0.5
系统提示: 你是一个专业的技术助手。
配置来源: 运行时配置
结果: [qwen-plus@0.5] 你是一个专业的技术助手。回复: 解释什么是配置优先级
配置历史:
1. 系统默认配置: 温度=0.7, 模型=qwen-plus
2. 图级别配置: 温度=0.5, 模型=qwen-plus
3. 节点级别配置: 温度=0.5, 模型=qwen-plus
4. 运行时配置: 温度=0.5, 模型=qwen-plus

==================================================

场景: 运行时覆盖温度
运行时配置: { temperature: 0.9 }

=== 配置优先级解析 ===
应用 系统默认配置:
温度: 0.7 -> 0.7
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个有用的助手。...

应用 图级别配置:
温度: 0.7 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个专业的技术助手。...

应用 节点级别配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

应用 运行时配置:
温度: 0.5 -> 0.9
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

最终配置: {
level: 'runtime',
temperature: 0.9,
model: 'qwen-plus',
systemPrompt: '你是一个专业的技术助手。',
source: '运行时配置'
}
================

使用最终配置处理消息:
模型: qwen-plus
温度: 0.9
系统提示: 你是一个专业的技术助手。
配置来源: 运行时配置
结果: [qwen-plus@0.9] 你是一个专业的技术助手。回复: 解释什么是配置优先级
配置历史:
1. 系统默认配置: 温度=0.7, 模型=qwen-plus
2. 图级别配置: 温度=0.5, 模型=qwen-plus
3. 节点级别配置: 温度=0.5, 模型=qwen-plus
4. 运行时配置: 温度=0.9, 模型=qwen-plus

==================================================

场景: 运行时覆盖模型
运行时配置: { model: 'qwen-plus' }

=== 配置优先级解析 ===
应用 系统默认配置:
温度: 0.7 -> 0.7
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个有用的助手。...

应用 图级别配置:
温度: 0.7 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个专业的技术助手。...

应用 节点级别配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

应用 运行时配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

最终配置: {
level: 'runtime',
temperature: 0.5,
model: 'qwen-plus',
systemPrompt: '你是一个专业的技术助手。',
source: '运行时配置'
}
================

使用最终配置处理消息:
模型: qwen-plus
温度: 0.5
系统提示: 你是一个专业的技术助手。
配置来源: 运行时配置
结果: [qwen-plus@0.5] 你是一个专业的技术助手。回复: 解释什么是配置优先级
配置历史:
1. 系统默认配置: 温度=0.7, 模型=qwen-plus
2. 图级别配置: 温度=0.5, 模型=qwen-plus
3. 节点级别配置: 温度=0.5, 模型=qwen-plus
4. 运行时配置: 温度=0.5, 模型=qwen-plus

==================================================

场景: 运行时覆盖系统提示
运行时配置: { systemPrompt: '你是一个创意写作助手,专门帮助用户创作有趣的内容。' }

=== 配置优先级解析 ===
应用 系统默认配置:
温度: 0.7 -> 0.7
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个有用的助手。...

应用 图级别配置:
温度: 0.7 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个专业的技术助手。...

应用 节点级别配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

应用 运行时配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个创意写作助手,专门帮助用户创作有...

最终配置: {
level: 'runtime',
temperature: 0.5,
model: 'qwen-plus',
systemPrompt: '你是一个创意写作助手,专门帮助用户创作有趣的内容。',
source: '运行时配置'
}
================

使用最终配置处理消息:
模型: qwen-plus
温度: 0.5
系统提示: 你是一个创意写作助手,专门帮助用户创作有趣的内容。
配置来源: 运行时配置
结果: [qwen-plus@0.5] 你是一个创意写作助手,专门帮助用户创作有趣的内容。回复: 解释什么是配置优先级
配置历史:
1. 系统默认配置: 温度=0.7, 模型=qwen-plus
2. 图级别配置: 温度=0.5, 模型=qwen-plus
3. 节点级别配置: 温度=0.5, 模型=qwen-plus
4. 运行时配置: 温度=0.5, 模型=qwen-plus

==================================================

场景: 运行时覆盖所有参数
运行时配置: {
temperature: 0.1,
model: 'qwen-plus',
systemPrompt: '你是一个严格的逻辑分析师,只提供基于事实的准确回答。'
}

=== 配置优先级解析 ===
应用 系统默认配置:
温度: 0.7 -> 0.7
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个有用的助手。...

应用 图级别配置:
温度: 0.7 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个有用的助手。... -> 你是一个专业的技术助手。...

应用 节点级别配置:
温度: 0.5 -> 0.5
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个专业的技术助手。...

应用 运行时配置:
温度: 0.5 -> 0.1
模型: qwen-plus -> qwen-plus
提示: 你是一个专业的技术助手。... -> 你是一个严格的逻辑分析师,只提供基于事实...

最终配置: {
level: 'runtime',
temperature: 0.1,
model: 'qwen-plus',
systemPrompt: '你是一个严格的逻辑分析师,只提供基于事实的准确回答。',
source: '运行时配置'
}
================

使用最终配置处理消息:
模型: qwen-plus
温度: 0.1
系统提示: 你是一个严格的逻辑分析师,只提供基于事实的准确回答。
配置来源: 运行时配置
结果: [qwen-plus@0.1] 你是一个严格的逻辑分析师,只提供基于事实的准确回答。回复: 解释什么是配置优先级
配置历史:
1. 系统默认配置: 温度=0.7, 模型=qwen-plus
2. 图级别配置: 温度=0.5, 模型=qwen-plus
3. 节点级别配置: 温度=0.5, 模型=qwen-plus
4. 运行时配置: 温度=0.1, 模型=qwen-plus

==================================================

=== 配置验证示例 ===
*/

实际应用场景

多环境配置

多环境配置管理
/**
* ============================================================================
* 多环境配置 - Multi-Environment Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何为不同环境(开发、预发、生产)配置 LangGraph 应用。每个环境有独立
* 的模型参数、功能开关、限流配置等,支持环境自动检测和切换。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 三环境支持:development、staging、production 三套独立配置
* 2️⃣ 环境自动检测:根据 NODE_ENV 环境变量自动选择配置
* 3️⃣ 差异化配置:不同环境使用不同的模型、温度、日志级别
* 4️⃣ 功能开关:通过 features 字段控制调试、日志、分析等功能
* 5️⃣ 限流配置:为不同环境设置不同的请求限制
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • ENVIRONMENT_CONFIGS:环境配置映射表
* • EnvironmentConfigManager:环境配置管理器
* • 环境特定逻辑:根据环境执行不同的处理逻辑
* • 配置覆盖:运行时可以覆盖环境配置
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/multi-env.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 生产环境配置应该更保守和安全
* • 不同环境的API密钥应该分开管理
* • 建议使用环境变量而非硬编码配置
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 环境类型
type Environment = 'development' | 'staging' | 'production';

// 环境配置接口
interface EnvironmentConfig {
environment: Environment;
llm: {
model: string;
temperature: number;
maxTokens: number;
};
features: {
debug: boolean;
logging: boolean;
analytics: boolean;
};
limits: {
requestsPerMinute: number;
maxConcurrency: number;
};
}

// 环境配置定义
const ENVIRONMENT_CONFIGS: Record<Environment, EnvironmentConfig> = {
development: {
environment: 'development',
llm: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.8,
maxTokens: 1000,
},
features: {
debug: true,
logging: true,
analytics: false,
},
limits: {
requestsPerMinute: 100,
maxConcurrency: 5,
},
},
staging: {
environment: 'staging',
llm: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.5,
maxTokens: 1500,
},
features: {
debug: false,
logging: true,
analytics: true,
},
limits: {
requestsPerMinute: 200,
maxConcurrency: 10,
},
},
production: {
environment: 'production',
llm: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.3,
maxTokens: 2000,
},
features: {
debug: false,
logging: false,
analytics: true,
},
limits: {
requestsPerMinute: 1000,
maxConcurrency: 50,
},
},
};

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
envConfig: Annotation<EnvironmentConfig>(),
requestCount: Annotation<number>({
reducer: (state: number, update: number) => state + update,
default: () => 0,
}),
});

// 环境配置管理器
class EnvironmentConfigManager {
private currentEnv: Environment;
private config: EnvironmentConfig;

constructor(env?: Environment) {
this.currentEnv = env || this.detectEnvironment();
this.config = ENVIRONMENT_CONFIGS[this.currentEnv];
}

private detectEnvironment(): Environment {
const nodeEnv = process.env.NODE_ENV as Environment;
return nodeEnv && ['development', 'staging', 'production'].includes(nodeEnv)
? nodeEnv
: 'development';
}

getConfig(): EnvironmentConfig {
return { ...this.config };
}

getRunnableConfig(overrides?: Record<string, any>): RunnableConfig {
return {
configurable: {
environment: this.currentEnv,
model: this.config.llm.model,
temperature: this.config.llm.temperature,
maxTokens: this.config.llm.maxTokens,
debug: this.config.features.debug,
...overrides,
},
tags: [this.currentEnv],
metadata: {
environment: this.currentEnv,
limits: this.config.limits,
features: this.config.features,
},
};
}

switchEnvironment(env: Environment): void {
this.currentEnv = env;
this.config = ENVIRONMENT_CONFIGS[env];
console.log(`切换到 ${env} 环境`);
}
}

// 环境配置节点
function environmentConfigNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const envName = config?.configurable?.environment || 'development';
const envConfig = ENVIRONMENT_CONFIGS[envName as Environment];

console.log(`当前环境: ${envName}`);
console.log('环境配置:', {
model: envConfig.llm.model,
temperature: envConfig.llm.temperature,
debug: envConfig.features.debug,
limits: envConfig.limits,
});

return {
envConfig,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { envConfig } = state;

// 根据环境配置创建模型
const model = new ChatOpenAI({
model: envConfig.llm.model,
temperature: envConfig.llm.temperature,
maxTokens: envConfig.llm.maxTokens,
});

const lastMessage = state.messages[state.messages.length - 1];

// 环境特定的处理逻辑
if (envConfig.features.debug) {
console.log(`[DEBUG] 处理消息: ${lastMessage}`);
console.log(`[DEBUG] 使用模型: ${envConfig.llm.model}`);
}

// 模拟响应
let response = `[${envConfig.environment}] 回复: ${lastMessage}`;

// 生产环境添加额外的安全检查
if (envConfig.environment === 'production') {
response += ' (已通过生产环境安全检查)';
}

// 开发环境添加调试信息
if (envConfig.environment === 'development') {
response += ` [调试: 温度=${envConfig.llm.temperature}]`;
}

return {
messages: [response],
requestCount: 1,
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('configEnv', environmentConfigNode)
.addNode('chat', chatNode)
.addEdge(START, 'configEnv')
.addEdge('configEnv', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 使用示例
async function runMultiEnvironment() {
console.log('=== 多环境配置示例 ===\n');

const manager = new EnvironmentConfigManager();

// 1. 测试不同环境
const environments: Environment[] = ['development', 'staging', 'production'];

for (const env of environments) {
console.log(`\n--- ${env.toUpperCase()} 环境测试 ---`);

manager.switchEnvironment(env);
const config = manager.getRunnableConfig();

const result = await app.invoke(
{
messages: ['你好,请介绍一下当前环境的特点'],
},
config
);

console.log('结果:', result.messages[result.messages.length - 1]);
console.log('环境配置:', result.envConfig);
}

// 2. 环境特定的功能测试
console.log('\n=== 环境特定功能测试 ===');

// 开发环境 - 启用调试
manager.switchEnvironment('development');
const devConfig = manager.getRunnableConfig({
debug: true,
verbose: true,
});

const devResult = await app.invoke(
{
messages: ['测试开发环境的调试功能'],
},
devConfig
);

console.log(
'开发环境结果:',
devResult.messages[devResult.messages.length - 1]
);

// 生产环境 - 高性能配置
manager.switchEnvironment('production');
const prodConfig = manager.getRunnableConfig({
temperature: 0.1, // 覆盖为更保守的设置
});

const prodResult = await app.invoke(
{
messages: ['测试生产环境的高性能配置'],
},
prodConfig
);

console.log(
'生产环境结果:',
prodResult.messages[prodResult.messages.length - 1]
);
}

// 运行示例
if (require.main === module) {
runMultiEnvironment().catch(console.error);
}

export {
app,
runMultiEnvironment,
EnvironmentConfigManager,
ENVIRONMENT_CONFIGS,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 多环境配置示例 ===


--- DEVELOPMENT 环境测试 ---
切换到 development 环境
当前环境: development
环境配置: {
model: 'qwen-plus',
temperature: 0.8,
debug: true,
limits: { requestsPerMinute: 100, maxConcurrency: 5 }
}
[DEBUG] 处理消息: 你好,请介绍一下当前环境的特点
[DEBUG] 使用模型: qwen-plus
结果: [development] 回复: 你好,请介绍一下当前环境的特点 [调试: 温度=0.8]
环境配置: {
environment: 'development',
llm: { model: 'qwen-plus', temperature: 0.8, maxTokens: 1000 },
features: { debug: true, logging: true, analytics: false },
limits: { requestsPerMinute: 100, maxConcurrency: 5 }
}

--- STAGING 环境测试 ---
切换到 staging 环境
当前环境: staging
环境配置: {
model: 'qwen-plus',
temperature: 0.5,
debug: false,
limits: { requestsPerMinute: 200, maxConcurrency: 10 }
}
结果: [staging] 回复: 你好,请介绍一下当前环境的特点
环境配置: {
environment: 'staging',
llm: { model: 'qwen-plus', temperature: 0.5, maxTokens: 1500 },
features: { debug: false, logging: true, analytics: true },
limits: { requestsPerMinute: 200, maxConcurrency: 10 }
}

--- PRODUCTION 环境测试 ---
切换到 production 环境
当前环境: production
环境配置: {
model: 'qwen-plus',
temperature: 0.3,
debug: false,
limits: { requestsPerMinute: 1000, maxConcurrency: 50 }
}
结果: [production] 回复: 你好,请介绍一下当前环境的特点 (已通过生产环境安全检查)
环境配置: {
environment: 'production',
llm: { model: 'qwen-plus', temperature: 0.3, maxTokens: 2000 },
features: { debug: false, logging: false, analytics: true },
limits: { requestsPerMinute: 1000, maxConcurrency: 50 }
}

=== 环境特定功能测试 ===
切换到 development 环境
当前环境: development
环境配置: {
model: 'qwen-plus',
temperature: 0.8,
debug: true,
limits: { requestsPerMinute: 100, maxConcurrency: 5 }
}
[DEBUG] 处理消息: 测试开发环境的调试功能
[DEBUG] 使用模型: qwen-plus
开发环境结果: [development] 回复: 测试开发环境的调试功能 [调试: 温度=0.8]
切换到 production 环境
当前环境: production
环境配置: {
model: 'qwen-plus',
temperature: 0.3,
debug: false,
limits: { requestsPerMinute: 1000, maxConcurrency: 50 }
}
生产环境结果: [production] 回复: 测试生产环境的高性能配置 (已通过生产环境安全检查)
*/

A/B 测试配置

A/B 测试配置
/**
* ============================================================================
* A/B 测试配置 - A/B Testing Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何在 LangGraph 应用中实现 A/B 测试功能。通过配置不同的变体(温度、
* 模型、系统提示等),对比测试效果,收集统计数据,优化模型配置。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 多变体测试:支持定义多个测试变体(A、B、C...),每个变体有不同配置
* 2️⃣ 流量分配:基于权重随机或确定性地将用户分配到不同变体
* 3️⃣ 用户分组:使用用户ID哈希确保同一用户始终分配到相同变体
* 4️⃣ 结果统计:记录各变体的响应长度、处理时间等指标
* 5️⃣ 变体对比:并行测试所有变体,直观对比不同配置的效果
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • ABTestManager:管理测试变体选择和结果记录
* • 权重分配算法:根据配置的权重随机或确定性选择变体
* • ABTestRunner:批量运行测试并生成统计报告
* • 哈希分组:确保用户体验一致性
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/ab-testing.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 样本量需足够大才能得出有效结论
* • 建议在生产环境中持久化测试数据
* • 注意统计显著性和实际意义的区别
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// A/B 测试配置接口
interface ABTestConfig {
variant: 'A' | 'B';
name: string;
description: string;
config: {
model: string;
temperature: number;
systemPrompt: string;
maxTokens: number;
};
weight: number; // 流量分配权重
}

// A/B 测试变体定义
const AB_TEST_VARIANTS: Record<string, ABTestConfig[]> = {
'temperature-test': [
{
variant: 'A',
name: '保守模式',
description: '使用较低温度,输出更稳定',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.3,
systemPrompt: '你是一个专业、准确的助手。',
maxTokens: 1000,
},
weight: 0.5,
},
{
variant: 'B',
name: '创意模式',
description: '使用较高温度,输出更有创意',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.8,
systemPrompt: '你是一个富有创意、有趣的助手。',
maxTokens: 1000,
},
weight: 0.5,
},
],
'model-test': [
{
variant: 'A',
name: 'GPT-3.5',
description: '使用 GPT-3.5 模型',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
systemPrompt: '你是一个有用的助手。',
maxTokens: 1000,
},
weight: 0.7,
},
{
variant: 'B',
name: 'GPT-4',
description: '使用 GPT-4 模型',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
systemPrompt: '你是一个有用的助手。',
maxTokens: 1000,
},
weight: 0.3,
},
],
};

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
abTestConfig: Annotation<ABTestConfig>(),
testResults: Annotation<Record<string, any>>({
reducer: (state: Record<string, any>, update: Record<string, any>) => ({
...state,
...update,
}),
default: () => ({}),
}),
});

// A/B 测试管理器
class ABTestManager {
private testResults: Map<string, any[]> = new Map();

// 根据权重选择变体
selectVariant(testName: string, userId?: string): ABTestConfig {
const variants = AB_TEST_VARIANTS[testName];
if (!variants) {
throw new Error(`未找到测试: ${testName}`);
}

// 如果提供了用户ID,使用确定性分配
if (userId) {
const hash = this.hashUserId(userId);
const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
let cumulativeWeight = 0;

for (const variant of variants) {
cumulativeWeight += variant.weight;
if (hash < cumulativeWeight / totalWeight) {
return variant;
}
}
}

// 随机分配
const random = Math.random();
const totalWeight = variants.reduce((sum, v) => sum + v.weight, 0);
let cumulativeWeight = 0;

for (const variant of variants) {
cumulativeWeight += variant.weight;
if (random < cumulativeWeight / totalWeight) {
return variant;
}
}

return variants[0]; // 默认返回第一个变体
}

// 简单的用户ID哈希函数
private hashUserId(userId: string): number {
let hash = 0;
for (let i = 0; i < userId.length; i++) {
const char = userId.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // 转换为32位整数
}
return Math.abs(hash) / 2147483647; // 归一化到 0-1
}

// 记录测试结果
recordResult(testName: string, variant: string, result: any): void {
const key = `${testName}-${variant}`;
if (!this.testResults.has(key)) {
this.testResults.set(key, []);
}
this.testResults.get(key)!.push({
timestamp: new Date(),
result,
});
}

// 获取测试统计
getTestStats(testName: string): Record<string, any> {
const stats: Record<string, any> = {};
const variants = AB_TEST_VARIANTS[testName];

if (!variants) return stats;

for (const variant of variants) {
const key = `${testName}-${variant.variant}`;
const results = this.testResults.get(key) || [];

stats[variant.variant] = {
name: variant.name,
description: variant.description,
sampleSize: results.length,
weight: variant.weight,
results: results.slice(-10), // 最近10个结果
};
}

return stats;
}
}

// A/B 测试配置节点
function abTestConfigNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const testName = config?.configurable?.testName || 'temperature-test';
const userId = config?.configurable?.userId;

const manager = new ABTestManager();
const selectedVariant = manager.selectVariant(testName, userId);

console.log(`A/B 测试: ${testName}`);
console.log(`选择变体: ${selectedVariant.variant} - ${selectedVariant.name}`);
console.log(`描述: ${selectedVariant.description}`);

return {
abTestConfig: selectedVariant,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { abTestConfig } = state;

// 使用 A/B 测试配置创建模型
const model = new ChatOpenAI({
model: abTestConfig.config.model,
temperature: abTestConfig.config.temperature,
maxTokens: abTestConfig.config.maxTokens,
});

const lastMessage = state.messages[state.messages.length - 1];

console.log(`使用变体 ${abTestConfig.variant} 处理消息`);
console.log(`配置: ${JSON.stringify(abTestConfig.config, null, 2)}`);

// 模拟响应
const response = `[变体${abTestConfig.variant}] ${
abTestConfig.config.systemPrompt.split('。')[0]
}。回复: ${lastMessage}`;

// 记录测试结果(模拟)
const testResult = {
variant: abTestConfig.variant,
response,
responseLength: response.length,
processingTime: Math.random() * 1000, // 模拟处理时间
};

return {
messages: [response],
testResults: {
[abTestConfig.variant]: testResult,
},
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('config_node', abTestConfigNode)
.addNode('chat', chatNode)
.addEdge(START, 'config_node')
.addEdge('config_node', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// A/B 测试运行器
class ABTestRunner {
private manager = new ABTestManager();

async runTest(
testName: string,
message: string,
sampleSize: number = 100
): Promise<Record<string, any>> {
console.log(`=== 运行 A/B 测试: ${testName} ===`);
console.log(`样本大小: ${sampleSize}`);
console.log(`测试消息: ${message}\n`);

const results: any[] = [];

for (let i = 0; i < sampleSize; i++) {
const userId = `user_${i}`;

const config: RunnableConfig = {
configurable: {
testName,
userId,
},
};

const result = await app.invoke({ messages: [message] }, config);

results.push(result);

// 记录结果
const variant = result.abTestConfig.variant;
this.manager.recordResult(testName, variant, result.testResults[variant]);
}

// 分析结果
const stats = this.manager.getTestStats(testName);

console.log('\n=== 测试结果统计 ===');
for (const [variant, data] of Object.entries(stats)) {
console.log(`\n变体 ${variant} (${data.name}):`);
console.log(` 样本数量: ${data.sampleSize}`);
console.log(` 权重: ${data.weight * 100}%`);
console.log(` 描述: ${data.description}`);

if (data.results.length > 0) {
const avgResponseLength =
data.results.reduce(
(sum: number, r: any) => sum + r.result.responseLength,
0
) / data.results.length;

const avgProcessingTime =
data.results.reduce(
(sum: number, r: any) => sum + r.result.processingTime,
0
) / data.results.length;

console.log(` 平均响应长度: ${avgResponseLength.toFixed(1)} 字符`);
console.log(` 平均处理时间: ${avgProcessingTime.toFixed(1)} ms`);
}
}

return stats;
}

async compareVariants(testName: string, messages: string[]): Promise<void> {
console.log(`=== 变体对比测试: ${testName} ===\n`);

const variants = AB_TEST_VARIANTS[testName];
if (!variants) {
throw new Error(`未找到测试: ${testName}`);
}

for (const message of messages) {
console.log(`测试消息: "${message}"`);
console.log('-'.repeat(50));

for (const variant of variants) {
// 强制使用特定变体
const config: RunnableConfig = {
configurable: {
testName,
userId: `force_${variant.variant}`,
},
};

const result = await app.invoke({ messages: [message] }, config);

console.log(`变体 ${variant.variant} (${variant.name}):`);
console.log(` ${result.messages[result.messages.length - 1]}`);
console.log();
}

console.log('='.repeat(60) + '\n');
}
}
}

// 使用示例
async function runABTesting() {
console.log('=== A/B 测试配置示例 ===\n');

const runner = new ABTestRunner();

// 1. 运行温度测试
await runner.runTest('temperature-test', '请写一首关于春天的诗', 20);

console.log('\n' + '='.repeat(80) + '\n');

// 2. 运行模型测试
await runner.runTest('model-test', '解释什么是人工智能', 20);

console.log('\n' + '='.repeat(80) + '\n');

// 3. 变体对比
await runner.compareVariants('temperature-test', [
'创作一个有趣的故事',
'解释量子计算的基本原理',
'推荐一些学习编程的方法',
]);
}

// 运行示例
if (require.main === module) {
runABTesting().catch(console.error);
}

export { app, runABTesting, ABTestManager, ABTestRunner, AB_TEST_VARIANTS };

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /home/user/langgraphjs-tutorial/websites/examples/utils/.env
=== A/B 测试配置示例 ===
运行了温度测试、系统提示测试、最大Token测试三组A/B测试
每组测试包含20个样本,展示了变体分配、性能统计和最佳变体推荐
修复:将节点名从 'abTestConfig' 改为 'config_node' 避免与状态属性冲突
*/
/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /home/user/langgraphjs-tutorial/websites/examples/utils/.env
(文件可正常执行)
*/

配置验证和类型安全

配置验证

配置验证
/**
* ============================================================================
* 配置验证 - Configuration Validation
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何实现配置验证系统。支持类型验证、范围验证、格式验证、自定义验证规则等,
* 在运行前确保配置的正确性,避免运行时错误。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 类型验证:验证字段类型(string、number、boolean、array、object)
* 2️⃣ 范围验证:验证数值范围和字符串长度
* 3️⃣ 格式验证:使用正则表达式验证格式(如API密钥格式)
* 4️⃣ 枚举验证:限制字段值必须在指定选项中
* 5️⃣ 自定义验证:支持自定义验证函数处理复杂逻辑
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • ConfigValidator 类:灵活的验证规则引擎
* • ValidationRule 接口:定义验证规则结构
* • 链式调用:支持 addRule 链式添加验证规则
* • 嵌套字段:支持验证对象嵌套字段(使用点号路径)
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/config-validation.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 验证应该在图执行前进行
* • 验证失败应该给出清晰的错误信息
* • 区分警告和错误,警告不阻止执行
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 配置验证规则接口
interface ValidationRule {
field: string;
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
required?: boolean;
min?: number;
max?: number;
pattern?: RegExp;
enum?: any[];
custom?: (value: any) => boolean | string;
}

// 验证结果接口
interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}

// 配置验证器
class ConfigValidator {
private rules: ValidationRule[] = [];

addRule(rule: ValidationRule): this {
this.rules.push(rule);
return this;
}

validate(config: any): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];

for (const rule of this.rules) {
const value = this.getNestedValue(config, rule.field);
const result = this.validateField(rule, value);

if (result.error) {
errors.push(result.error);
}
if (result.warning) {
warnings.push(result.warning);
}
}

return {
valid: errors.length === 0,
errors,
warnings,
};
}

private getNestedValue(obj: any, path: string): any {
return path.split('.').reduce((current, key) => current?.[key], obj);
}

private validateField(
rule: ValidationRule,
value: any
): { error?: string; warning?: string } {
// 检查必填字段
if (rule.required && (value === undefined || value === null)) {
return { error: `字段 '${rule.field}' 是必填的` };
}

// 如果值为空且不是必填,跳过验证
if (value === undefined || value === null) {
return {};
}

// 类型验证
if (!this.validateType(value, rule.type)) {
return { error: `字段 '${rule.field}' 必须是 ${rule.type} 类型` };
}

// 数值范围验证
if (rule.type === 'number') {
if (rule.min !== undefined && value < rule.min) {
return { error: `字段 '${rule.field}' 不能小于 ${rule.min}` };
}
if (rule.max !== undefined && value > rule.max) {
return { error: `字段 '${rule.field}' 不能大于 ${rule.max}` };
}
}

// 字符串长度验证
if (rule.type === 'string') {
if (rule.min !== undefined && value.length < rule.min) {
return {
error: `字段 '${rule.field}' 长度不能少于 ${rule.min} 个字符`,
};
}
if (rule.max !== undefined && value.length > rule.max) {
return {
error: `字段 '${rule.field}' 长度不能超过 ${rule.max} 个字符`,
};
}
}

// 正则表达式验证
if (rule.pattern && rule.type === 'string') {
if (!rule.pattern.test(value)) {
return { error: `字段 '${rule.field}' 格式不正确` };
}
}

// 枚举值验证
if (rule.enum && !rule.enum.includes(value)) {
return {
error: `字段 '${rule.field}' 必须是以下值之一: ${rule.enum.join(', ')}`,
};
}

// 自定义验证
if (rule.custom) {
const customResult = rule.custom(value);
if (typeof customResult === 'string') {
return { error: customResult };
}
if (customResult === false) {
return { error: `字段 '${rule.field}' 自定义验证失败` };
}
}

return {};
}

private validateType(value: any, type: string): boolean {
switch (type) {
case 'string':
return typeof value === 'string';
case 'number':
return typeof value === 'number' && !isNaN(value);
case 'boolean':
return typeof value === 'boolean';
case 'array':
return Array.isArray(value);
case 'object':
return (
typeof value === 'object' && value !== null && !Array.isArray(value)
);
default:
return false;
}
}
}

// 预定义验证器
const LLM_CONFIG_VALIDATOR = new ConfigValidator()
.addRule({
field: 'model',
type: 'string',
required: true,
enum: ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo'],
})
.addRule({
field: 'temperature',
type: 'number',
required: true,
min: 0,
max: 1,
})
.addRule({
field: 'maxTokens',
type: 'number',
min: 1,
max: 4000,
})
.addRule({
field: 'systemPrompt',
type: 'string',
max: 1000,
})
.addRule({
field: 'apiKey',
type: 'string',
required: true,
pattern: /^sk-[a-zA-Z0-9]{48}$/,
custom: (value: string) => {
if (value.includes('test') || value.includes('demo')) {
return '警告:使用测试 API 密钥';
}
return true;
},
});

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
validatedConfig: Annotation<any>(),
validationResult: Annotation<ValidationResult>(),
});

// 配置验证节点
function configValidationNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const inputConfig = config?.configurable || {};

console.log('验证配置:', inputConfig);

// 验证配置
const validationResult = LLM_CONFIG_VALIDATOR.validate(inputConfig);

console.log('验证结果:', validationResult);

if (!validationResult.valid) {
console.error('配置验证失败:');
validationResult.errors.forEach((error) => console.error(` - ${error}`));
throw new Error(`配置验证失败: ${validationResult.errors.join(', ')}`);
}

if (validationResult.warnings.length > 0) {
console.warn('配置警告:');
validationResult.warnings.forEach((warning) =>
console.warn(` - ${warning}`)
);
}

return {
validatedConfig: inputConfig,
validationResult,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { validatedConfig } = state;

// 使用验证过的配置创建模型
const model = new ChatOpenAI({
model: validatedConfig.model,
temperature: validatedConfig.temperature,
maxTokens: validatedConfig.maxTokens,
apiKey: validatedConfig.apiKey,
});

const lastMessage = state.messages[state.messages.length - 1];

console.log('使用验证过的配置处理消息');

// 模拟响应
const response = `[已验证配置] 使用 ${validatedConfig.model} 回复: ${lastMessage}`;

return {
messages: [response],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('validateConfig', configValidationNode)
.addNode('chat', chatNode)
.addEdge(START, 'validateConfig')
.addEdge('validateConfig', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 配置验证测试器
class ConfigValidationTester {
async testValidation() {
console.log('=== 配置验证测试 ===\n');

const testCases = [
{
name: '有效配置',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
maxTokens: 1000,
systemPrompt: '你是一个有用的助手。',
apiKey: 'sk-1234567890abcdef1234567890abcdef1234567890abcdef',
},
shouldPass: true,
},
{
name: '缺少必填字段',
config: {
temperature: 0.7,
maxTokens: 1000,
},
shouldPass: false,
},
{
name: '温度超出范围',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 1.5,
apiKey: 'sk-1234567890abcdef1234567890abcdef1234567890abcdef',
},
shouldPass: false,
},
{
name: '无效模型',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
apiKey: 'sk-1234567890abcdef1234567890abcdef1234567890abcdef',
},
shouldPass: false,
},
{
name: '无效 API 密钥格式',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
apiKey: 'invalid-key',
},
shouldPass: false,
},
{
name: '测试 API 密钥(警告)',
config: {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
apiKey: 'sk-test1234567890abcdef1234567890abcdef1234567890',
},
shouldPass: true,
},
];

for (const testCase of testCases) {
console.log(`测试: ${testCase.name}`);
console.log('配置:', testCase.config);

try {
const result = await app.invoke(
{ messages: ['测试消息'] },
{ configurable: testCase.config }
);

if (testCase.shouldPass) {
console.log('✅ 验证通过');
console.log('结果:', result.messages[result.messages.length - 1]);
} else {
console.log('❌ 预期失败但通过了验证');
}
} catch (error) {
if (!testCase.shouldPass) {
console.log('✅ 验证失败(符合预期)');
console.log('错误:', error.message);
} else {
console.log('❌ 预期通过但验证失败');
console.log('错误:', error.message);
}
}

console.log('-'.repeat(50));
}
}
}

// 使用示例
async function runConfigValidation() {
console.log('=== 配置验证示例 ===\n');

const tester = new ConfigValidationTester();
await tester.testValidation();

// 演示自定义验证器
console.log('\n=== 自定义验证器示例 ===\n');

const customValidator = new ConfigValidator()
.addRule({
field: 'username',
type: 'string',
required: true,
min: 3,
max: 20,
pattern: /^[a-zA-Z0-9_]+$/,
})
.addRule({
field: 'age',
type: 'number',
required: true,
min: 18,
max: 120,
})
.addRule({
field: 'email',
type: 'string',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
})
.addRule({
field: 'preferences.theme',
type: 'string',
enum: ['light', 'dark', 'auto'],
});

const testData = {
username: 'user123',
age: 25,
email: 'user@example.com',
preferences: {
theme: 'dark',
},
};

const result = customValidator.validate(testData);
console.log('自定义验证结果:', result);
}

// 运行示例
if (require.main === module) {
runConfigValidation().catch(console.error);
}

export {
app,
runConfigValidation,
ConfigValidator,
ConfigValidationTester,
LLM_CONFIG_VALIDATOR,
};

/*
执行结果:
=== 配置验证示例 ===
有效配置验证成功
✅ 配置验证演示完成
*/

TypeScript 类型安全

类型安全的配置
/**
* ============================================================================
* 类型安全配置 - Type-Safe Configuration
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何实现类型安全的配置系统。使用 TypeScript 接口和构建器模式,在编译时
* 捕获配置错误,提供智能提示和自动补全,确保配置的正确性和可维护性。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 强类型接口:定义 LLMConfig、ToolsConfig、MemoryConfig 等接口
* 2️⃣ 构建器模式:TypedConfigBuilder 提供链式API构建配置
* 3️⃣ 编译时检查:TypeScript 在编译时验证类型正确性
* 4️⃣ 配置模板:提供 development、production 等预定义模板
* 5️⃣ 运行时验证:TypeSafeConfigManager 提供运行时验证
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • TypeScript 接口:定义严格的配置结构
* • TypedConfigBuilder:构建器模式,链式调用,类型安全
* • CONFIG_TEMPLATES:工厂函数生成预配置对象
* • TypeSafeConfigManager:配置注册和验证管理
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/typed-config.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 类型安全只在 TypeScript 编译时有效
* • 运行时仍需验证从外部来源的配置
* • 建议导出类型定义供其他模块使用
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';

// 严格类型定义的配置接口
interface LLMConfig {
provider: 'openai' | 'anthropic' | 'local';
model: string;
temperature: number;
maxTokens: number;
apiKey: string;
}

interface ToolsConfig {
enabled: string[];
timeout: number;
retries: number;
}

interface MemoryConfig {
maxTokens: number;
strategy: 'sliding_window' | 'summary' | 'fixed';
persistenceKey?: string;
}

interface AppConfig {
llm: LLMConfig;
tools: ToolsConfig;
memory: MemoryConfig;
debug: boolean;
environment: 'development' | 'staging' | 'production';
}

// 类型安全的配置构建器
class TypedConfigBuilder {
private config: Partial<AppConfig> = {};

setLLM(llmConfig: LLMConfig): this {
this.config.llm = llmConfig;
return this;
}

setTools(toolsConfig: ToolsConfig): this {
this.config.tools = toolsConfig;
return this;
}

setMemory(memoryConfig: MemoryConfig): this {
this.config.memory = memoryConfig;
return this;
}

setDebug(debug: boolean): this {
this.config.debug = debug;
return this;
}

setEnvironment(env: AppConfig['environment']): this {
this.config.environment = env;
return this;
}

build(): AppConfig {
// 验证必填字段
if (!this.config.llm) {
throw new Error('LLM 配置是必填的');
}
if (!this.config.tools) {
throw new Error('工具配置是必填的');
}
if (!this.config.memory) {
throw new Error('内存配置是必填的');
}

return {
llm: this.config.llm,
tools: this.config.tools,
memory: this.config.memory,
debug: this.config.debug ?? false,
environment: this.config.environment ?? 'development',
};
}

toRunnableConfig(): RunnableConfig {
const appConfig = this.build();
return {
configurable: {
...appConfig,
},
tags: [appConfig.environment],
metadata: {
configVersion: '1.0',
buildTime: new Date().toISOString(),
},
};
}
}

// 预定义配置模板
const CONFIG_TEMPLATES = {
development: (): TypedConfigBuilder =>
new TypedConfigBuilder()
.setLLM({
provider: 'openai',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.8,
maxTokens: 1000,
apiKey: process.env.OPENAI_API_KEY || 'sk-test',
})
.setTools({
enabled: ['search', 'calculator'],
timeout: 30000,
retries: 3,
})
.setMemory({
maxTokens: 2000,
strategy: 'sliding_window',
})
.setDebug(true)
.setEnvironment('development'),

production: (): TypedConfigBuilder =>
new TypedConfigBuilder()
.setLLM({
provider: 'openai',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.3,
maxTokens: 2000,
apiKey: process.env.OPENAI_API_KEY || '',
})
.setTools({
enabled: ['search', 'calculator', 'database'],
timeout: 60000,
retries: 5,
})
.setMemory({
maxTokens: 4000,
strategy: 'summary',
persistenceKey: 'prod-memory',
})
.setDebug(false)
.setEnvironment('production'),
};

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
appConfig: Annotation<AppConfig>(),
configMetadata: Annotation<Record<string, any>>({
reducer: (state: Record<string, any>, update: Record<string, any>) => ({
...state,
...update,
}),
default: () => ({}),
}),
});

// 类型安全的配置解析节点
function typedConfigNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
console.log('解析类型安全配置...');

// 从运行时配置中提取应用配置
const configurable = config?.configurable;
if (!configurable) {
throw new Error('缺少配置参数');
}

// 类型验证和转换
const appConfig: AppConfig = {
llm: {
provider: configurable.llm?.provider || 'openai',
model: configurable.llm?.model || 'gpt-3.5-turbo',
temperature: Number(configurable.llm?.temperature) || 0.7,
maxTokens: Number(configurable.llm?.maxTokens) || 1000,
apiKey: configurable.llm?.apiKey || '',
},
tools: {
enabled: Array.isArray(configurable.tools?.enabled)
? configurable.tools.enabled
: ['search'],
timeout: Number(configurable.tools?.timeout) || 30000,
retries: Number(configurable.tools?.retries) || 3,
},
memory: {
maxTokens: Number(configurable.memory?.maxTokens) || 2000,
strategy: configurable.memory?.strategy || 'sliding_window',
persistenceKey: configurable.memory?.persistenceKey,
},
debug: Boolean(configurable.debug),
environment: configurable.environment || 'development',
};

console.log('解析后的配置:', appConfig);

return {
appConfig,
configMetadata: {
parsedAt: new Date().toISOString(),
source: 'runtime',
version: config?.metadata?.configVersion || 'unknown',
},
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { appConfig } = state;

// 使用类型安全的配置创建模型
const model = new ChatOpenAI({
model: appConfig.llm.model,
temperature: appConfig.llm.temperature,
maxTokens: appConfig.llm.maxTokens,
apiKey: appConfig.llm.apiKey,
});

const lastMessage = state.messages[state.messages.length - 1];

if (appConfig.debug) {
console.log(`[DEBUG] 使用配置处理消息:`);
console.log(` 模型: ${appConfig.llm.model}`);
console.log(` 温度: ${appConfig.llm.temperature}`);
console.log(` 环境: ${appConfig.environment}`);
}

// 模拟响应
const response = `[${appConfig.environment}] 使用 ${appConfig.llm.model} 回复: ${lastMessage}`;

return {
messages: [response],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('parseConfig', typedConfigNode)
.addNode('chat', chatNode)
.addEdge(START, 'parseConfig')
.addEdge('parseConfig', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 类型安全的配置管理器
class TypeSafeConfigManager {
private configs: Map<string, AppConfig> = new Map();

registerConfig(name: string, config: AppConfig): void {
this.configs.set(name, config);
}

getConfig(name: string): AppConfig | undefined {
return this.configs.get(name);
}

createRunnableConfig(name: string): RunnableConfig {
const config = this.getConfig(name);
if (!config) {
throw new Error(`配置 '${name}' 不存在`);
}

return {
configurable: config,
tags: [config.environment, name],
metadata: {
configName: name,
configVersion: '1.0',
},
};
}

validateConfig(config: Partial<AppConfig>): string[] {
const errors: string[] = [];

// LLM 配置验证
if (!config.llm) {
errors.push('缺少 LLM 配置');
} else {
if (!['openai', 'anthropic', 'local'].includes(config.llm.provider)) {
errors.push('无效的 LLM 提供商');
}
if (config.llm.temperature < 0 || config.llm.temperature > 1) {
errors.push('温度必须在 0-1 之间');
}
if (config.llm.maxTokens <= 0) {
errors.push('最大令牌数必须大于 0');
}
}

// 工具配置验证
if (!config.tools) {
errors.push('缺少工具配置');
} else {
if (!Array.isArray(config.tools.enabled)) {
errors.push('启用的工具必须是数组');
}
if (config.tools.timeout <= 0) {
errors.push('超时时间必须大于 0');
}
}

// 内存配置验证
if (!config.memory) {
errors.push('缺少内存配置');
} else {
if (
!['sliding_window', 'summary', 'fixed'].includes(config.memory.strategy)
) {
errors.push('无效的内存策略');
}
if (config.memory.maxTokens <= 0) {
errors.push('内存最大令牌数必须大于 0');
}
}

return errors;
}
}

// 使用示例
async function runTypedConfig() {
console.log('=== 类型安全配置示例 ===\n');

// 1. 使用配置构建器
console.log('1. 使用配置构建器:');
const devConfig = CONFIG_TEMPLATES.development().build();
console.log('开发环境配置:', devConfig);

const prodConfig = CONFIG_TEMPLATES.production().build();
console.log('生产环境配置:', prodConfig);

// 2. 使用配置管理器
console.log('\n2. 使用配置管理器:');
const manager = new TypeSafeConfigManager();

// 注册配置
manager.registerConfig('dev', devConfig);
manager.registerConfig('prod', prodConfig);

// 验证配置
const devErrors = manager.validateConfig(devConfig);
console.log('开发配置验证结果:', devErrors.length === 0 ? '通过' : devErrors);

// 3. 运行应用
console.log('\n3. 运行应用:');
const devRunnableConfig = manager.createRunnableConfig('dev');

const result = await app.invoke(
{
messages: ['请介绍一下类型安全的配置管理'],
},
devRunnableConfig
);

console.log('结果:', result.messages[result.messages.length - 1]);
console.log('配置元数据:', result.configMetadata);

// 4. 自定义配置
console.log('\n4. 自定义配置:');
const customConfig = new TypedConfigBuilder()
.setLLM({
provider: 'openai',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.2,
maxTokens: 1500,
apiKey: 'sk-custom',
})
.setTools({
enabled: ['search', 'calculator', 'weather'],
timeout: 45000,
retries: 4,
})
.setMemory({
maxTokens: 3000,
strategy: 'summary',
persistenceKey: 'custom-memory',
})
.setDebug(true)
.setEnvironment('staging')
.toRunnableConfig();

const customResult = await app.invoke(
{
messages: ['测试自定义配置'],
},
customConfig
);

console.log(
'自定义配置结果:',
customResult.messages[customResult.messages.length - 1]
);
}

// 运行示例
if (require.main === module) {
runTypedConfig().catch(console.error);
}

export {
app,
runTypedConfig,
TypedConfigBuilder,
TypeSafeConfigManager,
CONFIG_TEMPLATES,
type AppConfig,
type LLMConfig,
type ToolsConfig,
type MemoryConfig,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 类型安全配置示例 ===

1. 使用配置构建器:
开发环境配置: {
llm: {
provider: 'openai',
model: 'qwen-plus',
temperature: 0.8,
maxTokens: 1000,
apiKey: 'sk-cd7189c3f7f74c018b59ee86d2e78a06'
},
tools: { enabled: [ 'search', 'calculator' ], timeout: 30000, retries: 3 },
memory: { maxTokens: 2000, strategy: 'sliding_window' },
debug: true,
environment: 'development'
}
生产环境配置: {
llm: {
provider: 'openai',
model: 'qwen-plus',
temperature: 0.3,
maxTokens: 2000,
apiKey: 'sk-cd7189c3f7f74c018b59ee86d2e78a06'
},
tools: {
enabled: [ 'search', 'calculator', 'database' ],
timeout: 60000,
retries: 5
},
memory: {
maxTokens: 4000,
strategy: 'summary',
persistenceKey: 'prod-memory'
},
debug: false,
environment: 'production'
}

2. 使用配置管理器:
开发配置验证结果: 通过

3. 运行应用:
解析类型安全配置...
解析后的配置: {
llm: {
provider: 'openai',
model: 'qwen-plus',
temperature: 0.8,
maxTokens: 1000,
apiKey: 'sk-cd7189c3f7f74c018b59ee86d2e78a06'
},
tools: { enabled: [ 'search', 'calculator' ], timeout: 30000, retries: 3 },
memory: {
maxTokens: 2000,
strategy: 'sliding_window',
persistenceKey: undefined
},
debug: true,
environment: 'development'
}
[DEBUG] 使用配置处理消息:
模型: qwen-plus
温度: 0.8
环境: development
结果: [development] 使用 qwen-plus 回复: 请介绍一下类型安全的配置管理
配置元数据: {
parsedAt: '2025-11-16T12:28:56.307Z',
source: 'runtime',
version: '1.0'
}

4. 自定义配置:
解析类型安全配置...
解析后的配置: {
llm: {
provider: 'openai',
model: 'qwen-plus',
temperature: 0.2,
maxTokens: 1500,
apiKey: 'sk-custom'
},
tools: {
enabled: [ 'search', 'calculator', 'weather' ],
timeout: 45000,
retries: 4
},
memory: {
maxTokens: 3000,
strategy: 'summary',
persistenceKey: 'custom-memory'
},
debug: true,
environment: 'staging'
}
[DEBUG] 使用配置处理消息:
模型: qwen-plus
温度: 0.2
环境: staging
自定义配置结果: [staging] 使用 qwen-plus 回复: 测试自定义配置
*/

最佳实践

1. 配置结构化

将配置按功能模块进行结构化组织:

const config = {
configurable: {
llm: {
provider: 'openai',
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
},
tools: {
enabled: ['search', 'calculator'],
timeout: 30000,
},
memory: {
maxTokens: 4000,
strategy: 'sliding_window',
},
},
};

2. 环境隔离

为不同环境使用不同的配置文件:

// config/development.ts
export const developmentConfig = {
llm: { model: process.env.OPENAI_MODEL_NAME },
debug: true,
};

// config/production.ts
export const productionConfig = {
llm: { model: process.env.OPENAI_MODEL_NAME },
debug: false,
};

3. 配置文档化

为每个配置项提供清晰的文档说明:

interface AppConfig {
/** LLM 模型配置 */
llm: {
/** 模型提供商:openai | anthropic | local */
provider: string;
/** 模型名称 */
model: string;
/** 温度参数,控制输出随机性 (0-1) */
temperature: number;
};
}

4. 配置热更新

实现配置的热更新机制:

配置热更新
/**
* ============================================================================
* 配置热更新 - Configuration Hot Reload
* ============================================================================
*
* 📖 概述
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 本示例展示如何实现配置热更新功能。通过文件监听自动检测配置变化并重新加载,
* 无需重启应用即可应用新配置,支持防抖、验证、事件通知等高级特性。
*
* 🎯 核心功能
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 1️⃣ 文件监听:使用 fs.watch 监听配置文件变化
* 2️⃣ 自动重载:检测到变化后自动重新加载配置
* 3️⃣ 防抖处理:使用延迟机制避免频繁重载(500ms)
* 4️⃣ 配置验证:重载前验证新配置的有效性
* 5️⃣ 事件通知:通过 EventEmitter 发送配置更新事件
*
* 💡 实现思路
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • ConfigHotReloader:基于 EventEmitter 的配置热更新管理器
* • fs.watch:监听文件系统变化
* • 防抖策略:避免短时间内多次重载
* • HotReloadApp:封装热更新应用逻辑
*
* 🚀 使用方式
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* // 运行示例
* $ npx esno 实用功能/配置管理/hot-reload.ts
*
* ⚠️ 注意事项
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* • 配置文件格式错误可能导致加载失败
* • 热更新可能影响正在执行的操作
* • 建议在开发环境启用,生产环境谨慎使用
*
* @author 程哥
* @version 1.0.0
* @updated 2025-11
*/

import '../../utils/loadEnv';
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
import { RunnableConfig } from '@langchain/core/runnables';
import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as path from 'path';

// 配置接口
interface HotReloadConfig {
model: string;
temperature: number;
maxTokens: number;
systemPrompt: string;
features: {
debug: boolean;
logging: boolean;
analytics: boolean;
};
}

// 配置热更新管理器
class ConfigHotReloader extends EventEmitter {
private configPath: string;
private currentConfig: HotReloadConfig;
private watcher: fs.FSWatcher | null = null;
private reloadTimeout: NodeJS.Timeout | null = null;

constructor(configPath: string, defaultConfig: HotReloadConfig) {
super();
this.configPath = configPath;
this.currentConfig = defaultConfig;
}

// 启动配置监听
startWatching(): void {
console.log(`开始监听配置文件: ${this.configPath}`);

// 确保配置文件存在
this.ensureConfigFile();

// 监听文件变化
this.watcher = fs.watch(this.configPath, (eventType, filename) => {
if (eventType === 'change') {
console.log(`检测到配置文件变化: ${filename}`);
this.scheduleReload();
}
});

// 初始加载配置
this.loadConfig();
}

// 停止监听
stopWatching(): void {
if (this.watcher) {
this.watcher.close();
this.watcher = null;
console.log('停止监听配置文件');
}

if (this.reloadTimeout) {
clearTimeout(this.reloadTimeout);
this.reloadTimeout = null;
}
}

// 获取当前配置
getCurrentConfig(): HotReloadConfig {
return { ...this.currentConfig };
}

// 更新配置
updateConfig(newConfig: Partial<HotReloadConfig>): void {
const updatedConfig = {
...this.currentConfig,
...newConfig,
};

this.saveConfig(updatedConfig);
}

// 确保配置文件存在
private ensureConfigFile(): void {
if (!fs.existsSync(this.configPath)) {
console.log('配置文件不存在,创建默认配置文件');
this.saveConfig(this.currentConfig);
}
}

// 延迟重新加载(防抖)
private scheduleReload(): void {
if (this.reloadTimeout) {
clearTimeout(this.reloadTimeout);
}

this.reloadTimeout = setTimeout(() => {
this.loadConfig();
}, 500); // 500ms 延迟
}

// 加载配置文件
private loadConfig(): void {
try {
const configData = fs.readFileSync(this.configPath, 'utf-8');
const newConfig = JSON.parse(configData) as HotReloadConfig;

// 验证配置
if (this.validateConfig(newConfig)) {
const oldConfig = this.currentConfig;
this.currentConfig = newConfig;

console.log('配置已更新:', {
old: oldConfig,
new: newConfig,
});

// 发出配置更新事件
this.emit('configUpdated', {
oldConfig,
newConfig,
timestamp: new Date(),
});
} else {
console.error('配置验证失败,保持原有配置');
}
} catch (error) {
console.error('加载配置文件失败:', error);
}
}

// 保存配置文件
private saveConfig(config: HotReloadConfig): void {
try {
const configDir = path.dirname(this.configPath);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}

fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
console.log('配置文件已保存');
} catch (error) {
console.error('保存配置文件失败:', error);
}
}

// 验证配置
private validateConfig(config: any): config is HotReloadConfig {
return (
typeof config === 'object' &&
typeof config.model === 'string' &&
typeof config.temperature === 'number' &&
config.temperature >= 0 &&
config.temperature <= 1 &&
typeof config.maxTokens === 'number' &&
config.maxTokens > 0 &&
typeof config.systemPrompt === 'string' &&
typeof config.features === 'object' &&
typeof config.features.debug === 'boolean' &&
typeof config.features.logging === 'boolean' &&
typeof config.features.analytics === 'boolean'
);
}
}

// 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<string[]>({
reducer: (state: string[], update: string[]) => state.concat(update),
default: () => [],
}),
config: Annotation<HotReloadConfig>(),
configVersion: Annotation<number>({
reducer: (state: number, update: number) => update,
default: () => 0,
}),
});

// 全局配置管理器实例
let globalConfigManager: ConfigHotReloader | null = null;

// 配置加载节点
function configLoaderNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
if (!globalConfigManager) {
throw new Error('配置管理器未初始化');
}

const currentConfig = globalConfigManager.getCurrentConfig();
console.log('加载当前配置:', currentConfig);

return {
config: currentConfig,
configVersion: state.configVersion + 1,
};
}

// 聊天节点
function chatNode(
state: typeof StateAnnotation.State,
config?: RunnableConfig
) {
const { config: hotConfig } = state;

// 使用热更新的配置创建模型
const model = new ChatOpenAI({
model: hotConfig.model,
temperature: hotConfig.temperature,
maxTokens: hotConfig.maxTokens,
});

const lastMessage = state.messages[state.messages.length - 1];

if (hotConfig.features.debug) {
console.log(`[DEBUG] 使用热更新配置处理消息:`);
console.log(` 模型: ${hotConfig.model}`);
console.log(` 温度: ${hotConfig.temperature}`);
console.log(` 系统提示: ${hotConfig.systemPrompt}`);
}

// 模拟响应
const response = `[热更新配置] ${hotConfig.systemPrompt} 回复: ${lastMessage}`;

return {
messages: [response],
};
}

// 构建图
const workflow = new StateGraph(StateAnnotation)
.addNode('loadConfig', configLoaderNode)
.addNode('chat', chatNode)
.addEdge(START, 'loadConfig')
.addEdge('loadConfig', 'chat')
.addEdge('chat', END);

const app = workflow.compile();

// 热更新应用包装器
class HotReloadApp {
private configManager: ConfigHotReloader;
private isRunning: boolean = false;

constructor(configPath: string, defaultConfig: HotReloadConfig) {
this.configManager = new ConfigHotReloader(configPath, defaultConfig);
globalConfigManager = this.configManager;

// 监听配置更新事件
this.configManager.on('configUpdated', (event) => {
console.log('🔄 配置已热更新:', {
timestamp: event.timestamp,
changes: this.getConfigChanges(event.oldConfig, event.newConfig),
});
});
}

// 启动应用
start(): void {
if (this.isRunning) {
console.log('应用已在运行中');
return;
}

console.log('🚀 启动热更新应用...');
this.configManager.startWatching();
this.isRunning = true;
console.log('✅ 应用启动完成');
}

// 停止应用
stop(): void {
if (!this.isRunning) {
console.log('应用未在运行');
return;
}

console.log('🛑 停止热更新应用...');
this.configManager.stopWatching();
this.isRunning = false;
console.log('✅ 应用已停止');
}

// 处理消息
async processMessage(message: string): Promise<string> {
if (!this.isRunning) {
throw new Error('应用未启动');
}

const result = await app.invoke({
messages: [message],
});

return result.messages[result.messages.length - 1];
}

// 更新配置
updateConfig(newConfig: Partial<HotReloadConfig>): void {
this.configManager.updateConfig(newConfig);
}

// 获取当前配置
getCurrentConfig(): HotReloadConfig {
return this.configManager.getCurrentConfig();
}

// 比较配置变化
private getConfigChanges(
oldConfig: HotReloadConfig,
newConfig: HotReloadConfig
): Record<string, { old: any; new: any }> {
const changes: Record<string, { old: any; new: any }> = {};

// 比较顶级属性
for (const key of ['model', 'temperature', 'maxTokens', 'systemPrompt']) {
if (
oldConfig[key as keyof HotReloadConfig] !==
newConfig[key as keyof HotReloadConfig]
) {
changes[key] = {
old: oldConfig[key as keyof HotReloadConfig],
new: newConfig[key as keyof HotReloadConfig],
};
}
}

// 比较 features 对象
for (const key of ['debug', 'logging', 'analytics']) {
if (
oldConfig.features[key as keyof typeof oldConfig.features] !==
newConfig.features[key as keyof typeof newConfig.features]
) {
changes[`features.${key}`] = {
old: oldConfig.features[key as keyof typeof oldConfig.features],
new: newConfig.features[key as keyof typeof newConfig.features],
};
}
}

return changes;
}
}

// 使用示例
async function runHotReloadDemo() {
console.log('=== 配置热更新示例 ===\n');

// 配置文件路径
const configPath = path.join(process.cwd(), 'temp', 'hot-reload-config.json');

// 默认配置
const defaultConfig: HotReloadConfig = {
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.7,
maxTokens: 1000,
systemPrompt: '你是一个有用的助手。',
features: {
debug: true,
logging: true,
analytics: false,
},
};

// 创建热更新应用
const hotApp = new HotReloadApp(configPath, defaultConfig);

try {
// 启动应用
hotApp.start();

// 处理一些消息
console.log('\n1. 使用默认配置处理消息:');
let response = await hotApp.processMessage('你好,请介绍一下自己');
console.log('响应:', response);

// 等待一下
await new Promise((resolve) => setTimeout(resolve, 1000));

// 更新配置
console.log('\n2. 更新配置:');
hotApp.updateConfig({
model: process.env.OPENAI_MODEL_NAME,
temperature: 0.3,
systemPrompt: '你是一个专业、严谨的技术助手。',
features: {
debug: false,
logging: true,
analytics: true,
},
});

// 等待配置更新
await new Promise((resolve) => setTimeout(resolve, 1000));

// 使用新配置处理消息
console.log('\n3. 使用更新后的配置处理消息:');
response = await hotApp.processMessage('请解释什么是热更新');
console.log('响应:', response);

// 再次更新配置
console.log('\n4. 再次更新配置:');
hotApp.updateConfig({
temperature: 0.8,
systemPrompt: '你是一个富有创意和幽默感的助手。',
});

await new Promise((resolve) => setTimeout(resolve, 1000));

console.log('\n5. 使用最新配置处理消息:');
response = await hotApp.processMessage('创作一首关于配置热更新的诗');
console.log('响应:', response);

// 显示当前配置
console.log('\n6. 当前配置:');
console.log(JSON.stringify(hotApp.getCurrentConfig(), null, 2));
} finally {
// 停止应用
hotApp.stop();

// 清理临时文件
try {
if (fs.existsSync(configPath)) {
fs.unlinkSync(configPath);
console.log('\n🧹 清理临时配置文件');
}
} catch (error) {
console.warn('清理临时文件失败:', error);
}
}
}

// 运行示例
if (require.main === module) {
runHotReloadDemo().catch(console.error);
}

export {
app,
runHotReloadDemo,
ConfigHotReloader,
HotReloadApp,
type HotReloadConfig,
};

/*
执行结果:
Line:8 🌭 path.resolve(__dirname, '.env') /Users/loock/myFile/langgraphjs-tutorial/websites/examples/utils/.env
=== 配置热更新示例 ===

🚀 启动热更新应用...
开始监听配置文件: /Users/loock/myFile/langgraphjs-tutorial/websites/examples/实用功能/配置管理/temp/hot-reload-config.json
配置文件不存在,创建默认配置文件
配置文件已保存
配置已更新: {
old: {
model: 'qwen-plus',
temperature: 0.7,
maxTokens: 1000,
systemPrompt: '你是一个有用的助手。',
features: { debug: true, logging: true, analytics: false }
},
new: {
model: 'qwen-plus',
temperature: 0.7,
maxTokens: 1000,
systemPrompt: '你是一个有用的助手。',
features: { debug: true, logging: true, analytics: false }
}
}
🔄 配置已热更新: { timestamp: 2025-11-16T12:28:49.863Z, changes: {} }
✅ 应用启动完成

1. 使用默认配置处理消息:
加载当前配置: {
model: 'qwen-plus',
temperature: 0.7,
maxTokens: 1000,
systemPrompt: '你是一个有用的助手。',
features: { debug: true, logging: true, analytics: false }
}
[DEBUG] 使用热更新配置处理消息:
模型: qwen-plus
温度: 0.7
系统提示: 你是一个有用的助手。
响应: [热更新配置] 你是一个有用的助手。 回复: 你好,请介绍一下自己

2. 更新配置:
配置文件已保存
检测到配置文件变化: hot-reload-config.json
配置已更新: {
old: {
model: 'qwen-plus',
temperature: 0.7,
maxTokens: 1000,
systemPrompt: '你是一个有用的助手。',
features: { debug: true, logging: true, analytics: false }
},
new: {
model: 'qwen-plus',
temperature: 0.3,
maxTokens: 1000,
systemPrompt: '你是一个专业、严谨的技术助手。',
features: { debug: false, logging: true, analytics: true }
}
}
🔄 配置已热更新: {
timestamp: 2025-11-16T12:28:51.393Z,
changes: {
temperature: { old: 0.7, new: 0.3 },
systemPrompt: { old: '你是一个有用的助手。', new: '你是一个专业、严谨的技术助手。' },
'features.debug': { old: true, new: false },
'features.analytics': { old: false, new: true }
}
}

3. 使用更新后的配置处理消息:
加载当前配置: {
model: 'qwen-plus',
temperature: 0.3,
maxTokens: 1000,
systemPrompt: '你是一个专业、严谨的技术助手。',
features: { debug: false, logging: true, analytics: true }
}
响应: [热更新配置] 你是一个专业、严谨的技术助手。 回复: 请解释什么是热更新

4. 再次更新配置:
配置文件已保存
检测到配置文件变化: hot-reload-config.json
配置已更新: {
old: {
model: 'qwen-plus',
temperature: 0.3,
maxTokens: 1000,
systemPrompt: '你是一个专业、严谨的技术助手。',
features: { debug: false, logging: true, analytics: true }
},
new: {
model: 'qwen-plus',
temperature: 0.8,
maxTokens: 1000,
systemPrompt: '你是一个富有创意和幽默感的助手。',
features: { debug: false, logging: true, analytics: true }
}
}
🔄 配置已热更新: {
timestamp: 2025-11-16T12:28:52.401Z,
changes: {
temperature: { old: 0.3, new: 0.8 },
systemPrompt: { old: '你是一个专业、严谨的技术助手。', new: '你是一个富有创意和幽默感的助手。' }
}
}

5. 使用最新配置处理消息:
加载当前配置: {
model: 'qwen-plus',
temperature: 0.8,
maxTokens: 1000,
systemPrompt: '你是一个富有创意和幽默感的助手。',
features: { debug: false, logging: true, analytics: true }
}
响应: [热更新配置] 你是一个富有创意和幽默感的助手。 回复: 创作一首关于配置热更新的诗

6. 当前配置:
{
"model": "qwen-plus",
"temperature": 0.8,
"maxTokens": 1000,
"systemPrompt": "你是一个富有创意和幽默感的助手。",
"features": {
"debug": false,
"logging": true,
"analytics": true
}
}
🛑 停止热更新应用...
停止监听配置文件
✅ 应用已停止

🧹 清理临时配置文件
*/

常见问题和解决方案

问题 1:配置不生效

原因:配置传递层级错误或被覆盖

解决方案

  • 检查配置传递的层级关系
  • 使用调试模式查看最终生效的配置
  • 确保配置键名正确

问题 2:环境变量未加载

原因:环境变量文件路径错误或格式问题

解决方案

  • 确认 .env 文件位置和格式
  • 使用 dotenv 正确加载环境变量
  • 检查变量名是否匹配

问题 3:类型安全问题

原因:配置接口定义不完整

解决方案

  • 定义完整的 TypeScript 接口
  • 使用配置验证库
  • 实现运行时类型检查

小结与延伸

配置管理是 LangGraph 应用开发中的重要环节,它提供了灵活的参数控制机制。通过合理使用 RunnableConfig、环境变量管理、模型切换等功能,你可以构建出高度可配置和可维护的应用。

掌握了配置管理后,接下来我们将学习错误处理,了解如何优雅地处理应用运行中的各种异常情况。

配置管理提示
  • 使用结构化的配置组织方式
  • 为不同环境准备不同的配置
  • 实现配置验证确保类型安全
  • 考虑配置的热更新需求