Skip to content

输入/输出 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 自动推导功能。这种设计提供了多层保障:

  1. 编译时类型安全 - TypeScript 在编译时捕获类型错误
  2. IDE 支持 - 提供自动补全和智能提示
  3. 运行时验证 - 在运行时验证数据格式
  4. 错误信息 - 提供详细的验证错误信息

这些特性使得 LangChain V3 应用更加健壮和可维护,减少了因类型错误和数据格式问题导致的运行时错误。

在下一章中,我们将探讨分支与条件:RunnableBranch 如何实现路由,进一步提升应用的灵活性。