一招解决 AI 数据格式问题:让 AI 乖乖返回你要的数据结构

一招解决 AI 数据格式问题:让 AI 乖乖返回你要的数据结构

前言

在实际开发中,我们经常需要 AI 生成各种格式的数据。但是 AI 返回的数据格式往往不够规范,需要额外的处理。本文将介绍一个万能方法,让 AI 生成符合预期的数据结构。

核心思路

  1. 使用 Zod 定义数据结构
  2. 将 Zod Schema 转换为 JSON Schema
  3. 在提示词中加入格式说明
  4. 解析并验证 AI 返回的数据

代码实现

首先,我们需要安装必要的依赖:

bash 复制代码
npm install zod zod-to-json-schema

然后,实现核心代码:

ts 复制代码
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

class StructuredOutputParser<T extends z.ZodTypeAny> {
  schema: T;
  constructor(schema: T) {
    this.schema = schema;
  }
  getFormatInstructions() {
    return `输出应格式化为符合以下 JSON Schema 的 JSON 实例。
    例如,对于 Schema { "properties": { "foo": { "title": "Foo", "description": "a list of strings", "type": "array", "items": { "type": "string" } } }, "required": ["foo"] },
    对象 { "foo": ["bar", "baz"] } 是格式正确的 JSON 实例。
    对象 { "properties": { "foo": ["bar", "baz"] } } 不是 格式正确的 JSON 实例。
    以下是输出的 Schema:
\`\`\`json
${JSON.stringify(zodToJsonSchema(this.schema))}
\`\`\`
`;
  }
  async parse(text: string): Promise<z.infer<T>> {
    try {
      const json = text.includes('```') ? text.trim().split(/```(?:json)?/)[1] : text.trim();

      const escapedJson = json
        .replace(/"([^"\\]*(\\.[^"\\]*)*)"/g, (_match, capturedGroup) => {
          const escapedInsideQuotes = capturedGroup.replace(/\n/g, '\\n');
          return `"${escapedInsideQuotes}"`;
        })
        .replace(/\n/g, '');

      return await this.schema.parseAsync(JSON.parse(escapedJson));
    } catch (e) {
      throw new Error(`Failed to parse. Text: "${text}". Error: ${e}`);
    }
  }
}

class ParameterPrompt {
  private outputParser(schema: z.ZodTypeAny) {
    return new StructuredOutputParser(schema);
  }

  private createZodSchema(value: any): z.ZodTypeAny {
    if (typeof value === 'string') {
      return z.string();
    }
    if (typeof value === 'number') {
      return z.number();
    }
    if (typeof value === 'boolean') {
      return z.boolean();
    }
    if (Array.isArray(value)) {
      if (value.length > 0) {
        return z.array(this.createZodSchema(value[0]));
      }
      return z.array(z.any());
    }
    if (value === null) {
      return z.any().nullable();
    }
    if (typeof value === 'object' && value !== null) {
      return this.createObjectSchema(value);
    }
    return z.any();
  }

  private createObjectSchema(obj: Record<string, any>): z.ZodObject<any> {
    const schemaShape = Object.entries(obj).reduce(
      (acc, [key, value]) => {
        acc[key] = this.createZodSchema(value);
        return acc;
      },
      {} as Record<string, z.ZodTypeAny>,
    );

    return z.object(schemaShape);
  }

  public getPrompt(prompt: string, parameter: object) {
    const generatedZodSchema = this.createObjectSchema(parameter);
    const parser = this.outputParser(generatedZodSchema);
    const formatInstructions = parser.getFormatInstructions();
    const promptTemplate =
      prompt.indexOf('{{parameter}}') !== -1
        ? prompt.replace('{{parameter}}', formatInstructions)
        : prompt + formatInstructions;
    return { prompt: promptTemplate, parser };
  }
}

示例

  1. 生成人物信息
ts 复制代码
const { prompt, parser } = new ParameterPrompt().getPrompt(
    '请生成一个虚拟人物信息', 
    { 
        name: '名称',
        age: 18,
        description: '描述',
        hobbies: ['爱好']
    }
);

const result = await fetch('AI接口', {
    method: 'POST',
    body: JSON.stringify({ prompt })
});

const data = await parser.parse(result);
// 输出示例:
// {
//   "name": "李四",
//   "age": 25,
//   "description": "一个热爱生活的年轻人",
//   "hobbies": ["阅读", "旅行", "摄影"]
// }
  1. 生成商品数据
ts 复制代码
const { prompt, parser } = new ParameterPrompt().getPrompt(
    '请生成一个商品信息', 
    {
        id: 1,
        title: '商品名称',
        price: 99.9,
        tags: ['标签'],
        specs: {
            color: '颜色',
            size: '尺寸'
        }
    }
);
const result = await fetch('AI接口', {
    method: 'POST',
    body: JSON.stringify({ prompt })
});
const data = await parser.parse(result);
// 输出示例:
// {
//   "id": 101,
//   "title": "智能蓝牙耳机",
//   "price": 299.99,
//   "tags": ["电子产品", "蓝牙", "无线"],
//   "specs": {
//     "color": "黑色",
//     "size": "标准"
//   }
// }

总结

这个方案通过结合 Zod 的类型系统和自动化的 Schema 生成,提供了一种优雅的方式来处理 AI 生成的结构化数据。它不仅确保了数据格式的准确性,还提高了开发效率。 希望这个方法能帮助大家更好地使用 AI 生成所需的数据结构。如果觉得有帮助,欢迎点赞转发,也欢迎在评论区分享你的想法和建议!

参考资料

相关推荐
Larcher16 分钟前
新手也能学会,100行代码玩AI LOGO
前端·llm·html
徐子颐28 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭41 分钟前
如何理解HTML语义化
前端·html
jump6801 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu2 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花2 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋2 小时前
场景模拟:基础路由配置
前端
六月的可乐2 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程