一招解决 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 生成所需的数据结构。如果觉得有帮助,欢迎点赞转发,也欢迎在评论区分享你的想法和建议!

参考资料

相关推荐
只会安静敲代码的 小周5 分钟前
uniapp上传图片时(可选微信头像、相册、拍照)
前端·微信·uni-app
kovlistudio22 分钟前
红宝书第四十六讲:Node.js基础与API设计解析
前端·javascript·node.js
陈哥聊测试23 分钟前
这款自研底层框架,你说不定已经用上了
前端·后端·开源
蘑菇头爱平底锅39 分钟前
数字孪生-DTS-孪创城市-低空范围
前端·javascript·数据可视化
KenXu40 分钟前
Module Federation v0.12新特征详解
前端
三原1 小时前
前端微应用-乾坤(qiankun)原理分析-沙箱隔离(css)
前端·架构·前端框架
琦遇1 小时前
Vue3使用AntvG6写拓扑图,可添加修改删除节点和边
前端·javascript·vue.js
Luckyfif1 小时前
🗣️面试官:有一个比较经典的 case 也想探讨一下 「页面白屏如何排查?」❤️✨
前端·面试·开源
爱上大树的小猪1 小时前
【前端进阶】深入解析 Flexbox 布局中的 flex-shrink 与 gap 兼容性问题
前端·css·面试
南囝coding1 小时前
做Docx预览,一定要做这个神库!!
前端·vue.js·面试