输入/输出 Schema 的自动推导
TypeScript 泛型 + Zod 风格校验,实现类型安全的链式调用
在现代软件开发中,类型安全是确保代码质量和减少运行时错误的关键因素。LangChain V3 通过结合 TypeScript 泛型和类似 Zod 的验证机制,实现了强大的输入/输出 Schema 自动推导功能。这使得开发者能够在编译时捕获更多潜在错误,并获得更好的 IDE 支持。
TypeScript 泛型在 Runnable 中的应用
LangChain V3 利用 TypeScript 的泛型系统为每个 组件定义输入和输出类型:
typescript
interface Runnable<Input = any, Output = any> {
invoke(input: Input): Promise<Output>;
batch(inputs: Input[]): Promise<Output[]>;
stream(input: Input): AsyncGenerator<Output>;
}
// 具体实现示例
class StringLengthRunnable extends Runnable<string, number> {
async invoke(input: string): Promise<number> {
return input.length;
}
}
// 类型会被自动推导
const stringLength = new StringLengthRunnable();
// stringLength 的类型是 Runnable<string, number>Schema 推导的工作原理
LangChain V3 通过以下方式实现 Schema 的自动推导:
1. 基础类型推导
typescript
// PromptTemplate 可以从模板中推导输入类型
const template = new PromptTemplate({
template: "将 {input_language} 翻译为 {output_language}: {text}",
inputVariables: ["input_language", "output_language", "text"]
});
// TypeScript 可以推导出 template 的输入类型为:
// { input_language: string; output_language: string; text: string }2. 管道类型推导
当使用 pipe() 方法连接组件时,类型会自动推导和传播:
typescript
const template = new PromptTemplate({
template: "问题: {question}",
inputVariables: ["question"]
});
// template 类型: Runnable<{question: string}, string>
const llm = new ChatOpenAI();
// llm 类型: Runnable<string, string>
const chain = template.pipe(llm);
// chain 类型: Runnable<{question: string}, string>
// 如果继续连接其他组件
const parser = new StringOutputParser();
// parser 类型: Runnable<string, string>
const finalChain = chain.pipe(parser);
// finalChain 类型: Runnable<{question: string}, string>Zod 风格的验证集成
虽然 LangChain V3 不直接使用 Zod,但它实现了类似的验证机制:
typescript
// 定义输入 Schema
interface TranslationInput {
text: string;
sourceLanguage: string;
targetLanguage: string;
}
// 创建带验证的 Runnable
class ValidatedTranslationChain extends Runnable<TranslationInput, string> {
private chain: Runnable<TranslationInput, string>;
constructor() {
super();
this.chain = new PromptTemplate({
template: "将 {sourceLanguage} 翻译为 {targetLanguage}: {text}",
inputVariables: ["text", "sourceLanguage", "targetLanguage"]
}).pipe(new ChatOpenAI()).pipe(new StringOutputParser());
}
async invoke(input: TranslationInput): Promise<string> {
// 执行输入验证
this.validateInput(input);
return await this.chain.invoke(input);
}
private validateInput(input: TranslationInput): void {
if (!input.text || input.text.trim().length === 0) {
throw new Error("文本不能为空");
}
if (!input.sourceLanguage) {
throw new Error("源语言不能为空");
}
if (!input.targetLanguage) {
throw new Error("目标语言不能为空");
}
}
}自定义 Schema 验证
开发者可以创建自定义的 Schema 验证机制:
typescript
// 创建 Schema 验证装饰器
function ValidateSchema<T>(schema: any) {
return function(target: Runnable<T, any>, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(input: T) {
// 执行 Schema 验证
const validationResult = validateAgainstSchema(input, schema);
if (!validationResult.valid) {
throw new Error(`输入验证失败: ${validationResult.errors.join(', ')}`);
}
return originalMethod.apply(this, [input]);
};
};
}
// 使用装饰器
class CustomChain extends Runnable<UserInput, string> {
@ValidateSchema(UserInputSchema)
async invoke(input: UserInput): Promise<string> {
// 处理逻辑
}
}复杂类型的处理
LangChain V3 能够处理复杂的嵌套类型:
typescript
interface ComplexInput {
user: {
id: number;
profile: {
name: string;
preferences: string[];
};
};
context: {
history: Array<{
role: string;
content: string;
timestamp: Date;
}>;
};
}
const complexChain = new Runnable<ComplexInput, string>();
// TypeScript 会完整保留类型信息
// IDE 会提供完整的自动补全支持与 JSON Schema 的集成
LangChain V3 还支持将 TypeScript 类型转换为 JSON Schema:
typescript
// 从 TypeScript 接口生成 JSON Schema
function generateJsonSchema<T>(): any {
// 实现类型到 Schema 的转换
// 这通常通过装饰器或元数据反射实现
}
interface QAPair {
question: string;
answer: string;
confidence: number;
}
const schema = generateJsonSchema<QAPair>();
/*
生成的 Schema:
{
type: "object",
properties: {
question: { type: "string" },
answer: { type: "string" },
confidence: { type: "number", minimum: 0, maximum: 1 }
},
required: ["question", "answer", "confidence"]
}
*/运行时类型检查
除了编译时的类型检查,LangChain V3 还支持运行时类型检查:
typescript
class RuntimeValidatedRunnable<Input, Output> extends Runnable<Input, Output> {
private inputSchema: any;
private outputSchema: any;
constructor(
private runnable: Runnable<Input, Output>,
inputSchema: any,
outputSchema: any
) {
super();
this.inputSchema = inputSchema;
this.outputSchema = outputSchema;
}
async invoke(input: Input): Promise<Output> {
// 运行时输入验证
if (!validate(input, this.inputSchema)) {
throw new Error("输入验证失败");
}
const result = await this.runnable.invoke(input);
// 运行时输出验证
if (!validate(result, this.outputSchema)) {
throw new Error("输出验证失败");
}
return result;
}
}错误处理与类型安全
类型安全的 Schema 推导还能改进错误处理:
typescript
// 定义结构化错误类型
interface ValidationError {
field: string;
value: any;
error: string;
}
class SchemaValidationError extends Error {
constructor(public errors: ValidationError[]) {
super(`Schema 验证失败: ${errors.map(e => e.error).join(', ')}`);
this.name = 'SchemaValidationError';
}
}
// 在验证过程中收集详细错误信息
function validateWithDetailedErrors<T>(input: T, schema: any): ValidationError[] {
const errors: ValidationError[] = [];
// 执行详细验证并收集错误
// ...
return errors;
}实际应用示例
让我们看一个完整的实际应用示例:
typescript
// 定义输入和输出接口
interface TranslationRequest {
text: string;
sourceLang: string;
targetLang: string;
}
interface TranslationResponse {
originalText: string;
translatedText: string;
sourceLang: string;
targetLang: string;
confidence: number;
}
// 创建带 Schema 验证的翻译链
class TypedTranslationChain extends Runnable<TranslationRequest, TranslationResponse> {
private chain: Runnable<TranslationRequest, string>;
constructor() {
super();
this.chain = new PromptTemplate({
template: `将以下 {sourceLang} 文本翻译为 {targetLang}:
{text}
请提供翻译结果和您的置信度(0-1):`,
inputVariables: ["text", "sourceLang", "targetLang"]
})
.pipe(new ChatOpenAI())
.pipe(new StringOutputParser());
}
async invoke(input: TranslationRequest): Promise<TranslationResponse> {
// 编译时类型检查
this.validateInput(input);
const result = await this.chain.invoke(input);
// 解析和验证输出
return this.parseAndValidateOutput(result, input);
}
private validateInput(input: TranslationRequest): void {
if (!input.text?.trim()) {
throw new Error("文本不能为空");
}
if (!input.sourceLang) {
throw new Error("源语言不能为空");
}
if (!input.targetLang) {
throw new Error("目标语言不能为空");
}
}
private parseAndValidateOutput(
output: string,
input: TranslationRequest
): TranslationResponse {
// 解析 LLM 输出
const parsed = this.parseLLMOutput(output);
// 验证输出
if (typeof parsed.translatedText !== 'string') {
throw new Error("翻译结果格式错误");
}
if (typeof parsed.confidence !== 'number' ||
parsed.confidence < 0 ||
parsed.confidence > 1) {
throw new Error("置信度格式错误");
}
return {
originalText: input.text,
translatedText: parsed.translatedText,
sourceLang: input.sourceLang,
targetLang: input.targetLang,
confidence: parsed.confidence
};
}
private parseLLMOutput(output: string): any {
// 简化的解析逻辑
try {
return JSON.parse(output);
} catch {
// 如果 JSON 解析失败,尝试其他解析方式
return {
translatedText: output.trim(),
confidence: 0.8
};
}
}
}
// 使用示例
const translator = new TypedTranslationChain();
// TypeScript 会在编译时检查类型
const result = await translator.invoke({
text: "Hello, world!",
sourceLang: "English",
targetLang: "Chinese"
});
// result 的类型是 TranslationResponse
// IDE 会提供完整的字段自动补全
console.log(result.translatedText);
console.log(result.confidence);总结
LangChain V3 通过 TypeScript 泛型和类似 Zod 的验证机制,实现了强大的输入/输出 Schema 自动推导功能。这种设计提供了多层保障:
- 编译时类型安全 - TypeScript 在编译时捕获类型错误
- IDE 支持 - 提供自动补全和智能提示
- 运行时验证 - 在运行时验证数据格式
- 错误信息 - 提供详细的验证错误信息
这些特性使得 LangChain V3 应用更加健壮和可维护,减少了因类型错误和数据格式问题导致的运行时错误。
在下一章中,我们将探讨分支与条件:RunnableBranch 如何实现路由,进一步提升应用的灵活性。