Skip to content

PromptTemplate 高级特性:partial、自定义格式器、安全转义

在上一章中,我们探讨了 PromptTemplate 如何从简单的字符串模板演进为完整的 实现。本章将深入探讨 PromptTemplate 的高级特性,包括 partial 模板、自定义格式器和安全转义机制,这些特性使得 PromptTemplate 更加灵活和安全。

Partial Templates(部分模板)

Partial Templates 允许我们预先填充模板中的一部分变量,创建一个新的、更具体的模板。这对于创建模板的变体非常有用。

基本实现

typescript
class PromptTemplate extends Runnable<any, string> {
  private template: string;
  private inputVariables: string[];
  
  constructor(config: { template: string; inputVariables: string[] }) {
    super();
    this.template = config.template;
    this.inputVariables = config.inputVariables;
  }
  
  // Partial 方法实现
  partial(values: Record<string, any>): PromptTemplate {
    let newTemplate = this.template;
    const newInputVariables = [...this.inputVariables];
    
    // 替换提供的变量值
    for (const [key, value] of Object.entries(values)) {
      // 在模板中替换变量
      newTemplate = newTemplate.replace(
        new RegExp(`\\{${key}\\}`, 'g'), 
        this.escapeValue(String(value))
      );
      
      // 从输入变量列表中移除已填充的变量
      const index = newInputVariables.indexOf(key);
      if (index > -1) {
        newInputVariables.splice(index, 1);
      }
    }
    
    return new PromptTemplate({
      template: newTemplate,
      inputVariables: newInputVariables
    });
  }
  
  private escapeValue(value: string): string {
    // 简单的转义实现
    return value.replace(/\$/g, '$$$$'); // 转义 $ 字符
  }
  
  async invoke(input: Record<string, any>): Promise<string> {
    return this.format(input);
  }
  
  format(input: Record<string, any>): string {
    let result = this.template;
    
    for (const variable of this.inputVariables) {
      if (!(variable in input)) {
        throw new Error(`缺少必需的变量: ${variable}`);
      }
      
      result = result.replace(
        new RegExp(`\\{${variable}\\}`, 'g'), 
        this.escapeValue(String(input[variable]))
      );
    }
    
    return result;
  }
}

实际应用示例

typescript
// 创建基础模板
const baseTranslationTemplate = new PromptTemplate({
  template: "将 {sourceLanguage} 翻译为 {targetLanguage}: {text}",
  inputVariables: ["sourceLanguage", "targetLanguage", "text"]
});

// 创建特定语言的模板
const enToZhTemplate = baseTranslationTemplate.partial({
  sourceLanguage: "英语",
  targetLanguage: "中文"
});

const frToEsTemplate = baseTranslationTemplate.partial({
  sourceLanguage: "法语",
  targetLanguage: "西班牙语"
});

// 使用部分模板
const result1 = await enToZhTemplate.invoke({
  text: "Hello, world!"
});
// 输出: "将 英语 翻译为 中文: Hello, world!"

const result2 = await frToEsTemplate.invoke({
  text: "Bonjour, le monde!"
});
// 输出: "将 法语 翻译为 西班牙语: Bonjour, le monde!"

复杂的 Partial 应用

Partial Templates 还可以用于创建更复杂的模板变体:

typescript
// 创建一个复杂的模板
const complexTemplate = new PromptTemplate({
  template: `角色: {role}
任务: {task}
上下文: {context}
输入: {input}
输出格式: {format}
约束: {constraints}`,
  inputVariables: ["role", "task", "context", "input", "format", "constraints"]
});

// 创建特定角色的模板
const expertTemplate = complexTemplate.partial({
  role: "领域专家",
  constraints: "回答必须准确、专业,使用领域术语"
});

const teacherTemplate = complexTemplate.partial({
  role: "教师",
  constraints: "回答应该通俗易懂,适合初学者理解"
});

// 使用特定角色模板
const expertResponse = await expertTemplate.invoke({
  task: "解释量子力学",
  context: "用户是物理学博士",
  input: "什么是量子纠缠?",
  format: "技术术语 + 数学公式"
});

const teacherResponse = await teacherTemplate.invoke({
  task: "解释量子力学",
  context: "用户是高中生",
  input: "什么是量子纠缠?",
  format: "类比 + 简单解释"
});

自定义格式器

自定义格式器允许开发者定义自己的模板格式化逻辑,提供更大的灵活性。

实现自定义格式器

typescript
interface TemplateFormatter {
  format(template: string, input: Record<string, any>): string;
}

class PromptTemplate extends Runnable<any, string> {
  private template: string;
  private inputVariables: string[];
  private formatter: TemplateFormatter;
  
  constructor(config: {
    template: string;
    inputVariables: string[];
    formatter?: TemplateFormatter;
  }) {
    super();
    this.template = config.template;
    this.inputVariables = config.inputVariables;
    this.formatter = config.formatter || new DefaultFormatter();
  }
  
  async invoke(input: Record<string, any>): Promise<string> {
    return this.formatter.format(this.template, input);
  }
}

// 默认格式器
class DefaultFormatter implements TemplateFormatter {
  format(template: string, input: Record<string, any>): string {
    let result = template;
    
    for (const [key, value] of Object.entries(input)) {
      result = result.replace(
        new RegExp(`\\{${key}\\}`, 'g'), 
        String(value)
      );
    }
    
    return result;
  }
}

// JSON 格式器
class JsonFormatter implements TemplateFormatter {
  format(template: string, input: Record<string, any>): string {
    // 将输入转换为 JSON 格式并插入到模板中
    const jsonInput = JSON.stringify(input, null, 2);
    return template.replace('{input}', jsonInput);
  }
}

// Markdown 格式器
class MarkdownFormatter implements TemplateFormatter {
  format(template: string, input: Record<string, any>): string {
    let result = template;
    
    for (const [key, value] of Object.entries(input)) {
      // 将值格式化为 Markdown
      const formattedValue = this.formatAsMarkdown(value);
      result = result.replace(
        new RegExp(`\\{${key}\\}`, 'g'), 
        formattedValue
      );
    }
    
    return result;
  }
  
  private formatAsMarkdown(value: any): string {
    if (Array.isArray(value)) {
      return value.map((item, index) => `${index + 1}. ${item}`).join('\n');
    }
    
    if (typeof value === 'object' && value !== null) {
      return Object.entries(value)
        .map(([key, val]) => `**${key}**: ${val}`)
        .join('\n');
    }
    
    return String(value);
  }
}

使用自定义格式器

typescript
// 使用 JSON 格式器
const jsonTemplate = new PromptTemplate({
  template: `分析以下 JSON 数据:
{input}

请提供分析结果:`,
  inputVariables: ["input"],
  formatter: new JsonFormatter()
});

const jsonData = {
  users: [
    { name: "张三", age: 25 },
    { name: "李四", age: 30 }
  ],
  total: 2
};

const jsonResult = await jsonTemplate.invoke({ input: jsonData });
/* 输出:
分析以下 JSON 数据:
{
  "users": [
    {
      "name": "张三",
      "age": 25
    },
    {
      "name": "李四",
      "age": 30
    }
  ],
  "total": 2
}

请提供分析结果:
*/

// 使用 Markdown 格式器
const markdownTemplate = new PromptTemplate({
  template: `用户信息:
{userInfo}

任务列表:
{tasks}`,
  inputVariables: ["userInfo", "tasks"],
  formatter: new MarkdownFormatter()
});

const markdownResult = await markdownTemplate.invoke({
  userInfo: { 姓名: "王五", 职位: "工程师", 部门: "研发部" },
  tasks: ["完成项目A", "审查代码", "参加会议"]
});
/* 输出:
用户信息:
**姓名**: 王五
**职位**: 工程师
**部门**: 研发部

任务列表:
1. 完成项目A
2. 审查代码
3. 参加会议
*/

安全转义机制

安全转义机制防止恶意输入导致的提示注入攻击,是构建安全 LLM 应用的重要组成部分。

实现安全转义

typescript
class SecurePromptTemplate extends PromptTemplate {
  private escapeHtml: boolean;
  private escapeSpecialChars: boolean;
  
  constructor(config: {
    template: string;
    inputVariables: string[];
    escapeHtml?: boolean;
    escapeSpecialChars?: boolean;
  }) {
    super(config);
    this.escapeHtml = config.escapeHtml ?? true;
    this.escapeSpecialChars = config.escapeSpecialChars ?? true;
  }
  
  async invoke(input: Record<string, any>): Promise<string> {
    // 对输入进行安全转义
    const escapedInput = this.escapeInput(input);
    return super.format(escapedInput);
  }
  
  private escapeInput(input: Record<string, any>): Record<string, any> {
    const escapedInput: Record<string, any> = {};
    
    for (const [key, value] of Object.entries(input)) {
      if (typeof value === 'string') {
        escapedInput[key] = this.escapeString(value);
      } else {
        escapedInput[key] = value;
      }
    }
    
    return escapedInput;
  }
  
  private escapeString(str: string): string {
    let escaped = str;
    
    // HTML 转义
    if (this.escapeHtml) {
      escaped = escaped
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#x27;');
    }
    
    // 特殊字符转义
    if (this.escapeSpecialChars) {
      escaped = escaped
        .replace(/\{/g, '\\{')
        .replace(/\}/g, '\\}')
        .replace(/\$/g, '\\$')
        .replace(/\\/g, '\\\\');
    }
    
    return escaped;
  }
}

安全转义的实际应用

typescript
// 创建安全的提示模板
const secureTemplate = new SecurePromptTemplate({
  template: "用户输入: {userInput}\n请分析此输入:",
  inputVariables: ["userInput"]
});

// 测试安全转义
const maliciousInput = "正常文本 {malicious_code} <script>alert('xss')</script>";
const secureResult = await secureTemplate.invoke({
  userInput: maliciousInput
});

/* 输出:
用户输入: 正常文本 \{malicious_code\} &lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;
请分析此输入:
*/

// 对比不安全的处理
const unsafeTemplate = new PromptTemplate({
  template: "用户输入: {userInput}\n请分析此输入:",
  inputVariables: ["userInput"]
});

const unsafeResult = await unsafeTemplate.invoke({
  userInput: maliciousInput
});

/* 输出(可能存在问题):
用户输入: 正常文本 {malicious_code} <script>alert('xss')</script>
请分析此输入:
*/

综合应用示例

让我们看一个综合应用所有高级特性的示例:

typescript
// 创建一个功能完整的高级提示模板系统
class AdvancedPromptTemplate extends Runnable<Record<string, any>, string> {
  private template: string;
  private inputVariables: string[];
  private formatter: TemplateFormatter;
  private escapeHtml: boolean;
  private escapeSpecialChars: boolean;
  
  constructor(config: {
    template: string;
    inputVariables: string[];
    formatter?: TemplateFormatter;
    escapeHtml?: boolean;
    escapeSpecialChars?: boolean;
  }) {
    super();
    this.template = config.template;
    this.inputVariables = config.inputVariables;
    this.formatter = config.formatter || new DefaultFormatter();
    this.escapeHtml = config.escapeHtml ?? true;
    this.escapeSpecialChars = config.escapeSpecialChars ?? true;
  }
  
  // Partial 方法
  partial(values: Record<string, any>): AdvancedPromptTemplate {
    let newTemplate = this.template;
    const newInputVariables = [...this.inputVariables];
    
    for (const [key, value] of Object.entries(values)) {
      const escapedValue = this.escapeString(String(value));
      newTemplate = newTemplate.replace(
        new RegExp(`\\{${key}\\}`, 'g'), 
        escapedValue
      );
      
      const index = newInputVariables.indexOf(key);
      if (index > -1) {
        newInputVariables.splice(index, 1);
      }
    }
    
    return new AdvancedPromptTemplate({
      template: newTemplate,
      inputVariables: newInputVariables,
      formatter: this.formatter,
      escapeHtml: this.escapeHtml,
      escapeSpecialChars: this.escapeSpecialChars
    });
  }
  
  async invoke(input: Record<string, any>): Promise<string> {
    // 安全转义
    const escapedInput = this.escapeInput(input);
    // 使用自定义格式器
    return this.formatter.format(this.template, escapedInput);
  }
  
  private escapeInput(input: Record<string, any>): Record<string, any> {
    const escapedInput: Record<string, any> = {};
    
    for (const [key, value] of Object.entries(input)) {
      if (typeof value === 'string') {
        escapedInput[key] = this.escapeString(value);
      } else {
        escapedInput[key] = value;
      }
    }
    
    return escapedInput;
  }
  
  private escapeString(str: string): string {
    let escaped = str;
    
    if (this.escapeHtml) {
      escaped = escaped
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#x27;');
    }
    
    if (this.escapeSpecialChars) {
      escaped = escaped
        .replace(/\{/g, '\\{')
        .replace(/\}/g, '\\}')
        .replace(/\$/g, '\\$')
        .replace(/\\/g, '\\\\');
    }
    
    return escaped;
  }
}

// 使用示例
const baseTemplate = new AdvancedPromptTemplate({
  template: `任务: {task}
输入数据: {data}
输出要求: {requirements}`,
  inputVariables: ["task", "data", "requirements"],
  formatter: new MarkdownFormatter()
});

// 创建特定任务模板
const analysisTemplate = baseTemplate.partial({
  task: "数据分析",
  requirements: "提供详细的统计分析和可视化建议"
});

// 使用模板处理可能的恶意输入
const result = await analysisTemplate.invoke({
  data: "用户数据包含 <script> 恶意代码和 {注入尝试}"
});

console.log(result);

总结

PromptTemplate 的高级特性包括 partial templates、自定义格式器和安全转义机制,这些特性大大增强了其灵活性和安全性:

  1. Partial Templates 允许创建模板的特定变体,通过预先填充部分变量来简化使用
  2. 自定义格式器 提供了灵活的模板格式化机制,支持不同的输出格式需求
  3. 安全转义机制 防止提示注入攻击,确保应用的安全性

这些高级特性使得 PromptTemplate 成为一个功能强大且安全的组件,能够满足各种复杂的应用场景需求。

在下一章中,我们将探讨动态提示:如何根据输入选择不同模板,进一步提升 PromptTemplate 的灵活性。