当TS遇上AI,会发生什么?

人工智能1年前发布 zjch2
30 00

人工智能现在每天都在发展,大型语言模型变得越来越强大。工作中使用AI工具帮忙,将大大提高了工作效率,只需敲几个字符,按Tab键,代码就智能完成。

当TS遇上AI,会发生什么?

除了代码补全之外,我们还可以让AI帮助我们自动化功能并返回所需的JSON数据。

先让我们看一个例子:

// index.ts
interface Height {
  meters: number;
  feet: number;
}


interface Mountain {
  name: string;
  height: Height;
}


// @ts-ignore
// @magic
async function getHighestMountain(): Promise<Mountain> {
  // Return the highest mountain
}


(async () => {
  console.log(await getHighestMountain());
})();

在上面的代码中,我们定义了一个 getHighestMountain 异步函数来获取世界上最高峰的信息,它的返回值是 Mountain 接口定义的数据结构。函数内部没有具体的实现,我们只是通过注释描述函数需要做什么。

编译并执行上述代码后,控制台会输出如下结果:

{ name: 'Mount Everest', height: { meters: 8848, feet: 29029 } }

世界最高的山峰是珠穆朗玛峰,它是喜马拉雅山脉的主峰,也是世界最高峰,海拔8848.86米,是不是很神奇?

接下来我就来揭秘getHighestMountain函数的秘密。

为了了解 getHighestMountain 异步函数内部做了什么,我们看一下编译后的 JS 代码:

const { fetchCompletion } = require("@jumploops/magic");


// @ts-ignore
// @magic
function getHighestMountain() {
    return __awaiter(this, void 0, void 0, function* () {
        return yield fetchCompletion("{\n  // Return the highest mountain\n}", {
            schema: "{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"height\":{\"$ref\":\"#/definitions/Height\"}},\"required\":[\"height\",\"name\"],\"definitions\":{\"Height\":{\"type\":\"object\",\"properties\":{\"meters\":{\"type\":\"number\"},\"feet\":{\"type\":\"number\"}},\"required\":[\"feet\",\"meters\"]}},\"$schema\":\"http://json-schema.org/draft-07/schema#\"}"
        });
    });
}

从上面的代码可以看出,@jumploops/magic 库中的 fetchCompletion 函数在 getHighestMountain 函数内部被调用。

从这个函数的参数中,我们看到了之前TS函数的函数注释,此外,我们还看到了一个包含schema属性的对象。该属性的值为Mountain接口对应的JSON Schema对象。

接下来我们重点分析@jumploops/magic库中的fetchCompletion函数。该函数定义在fetchCompletion.ts文件中,其内部处理流程分为3步:

  • 组装 Chat Completions API 所需的提示;
  • 调用Chat Completions API获取响应结果;
  • 解析响应结果并使用 JSON 模式验证响应对象。
// fetchCompletion.ts
export async function fetchCompletion(
  existingFunction: string, 
  { schema }: { schema: any }) {
  let completion;


  // (1)
  const prompt = `
    You are a robotic assistant. Your only language is code. You only respond with valid JSON. Nothing but JSON. 
 For example, if you're planning to return:
      { "list": [ { "name": "Alice" }, { "name": "Bob" }, { "name": "Carol"}] } 
    Instead just return:
      [ { "name": "Alice" }, { "name": "Bob" }, { "name": "Carol"}]
    ...


    Prompt: ${existingFunction.replace('{', '')
     .replace('}', '').replace('//', '').replace('\n', '')}


    JSON Schema: 
    \`\`\`
      ${JSON.stringify(JSON.parse(schema), null, 2)}
    \`\`\`
  `;




  // (2)
  try {
    completion = await openai.createChatCompletion({
      model: process.env.OPENAI_MODEL ?
       process.env.OPENAI_MODEL : 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: prompt }],
    });
  } catch (err) {
    console.error(err);
    return;
  }


  const response = JSON.parse(completion.data.choices[0].message.content);


  // (3)
  if (!validateAPIResponse(response, JSON.parse(schema))) {
    throw new Error("Invalid JSON response from LLM");
  }


  return JSON.parse(completion.data.choices[0].message.content);
}

在Prompt中,我们为AI设置了角色,并为它准备了一些例子来引导它返回有效的JSON格式。

调用Chat Completions API获取响应结果,直接使用openai库提供的createChatCompletion API。

解析得到响应结果后,会调用validateAPIResponse函数对响应对象进行验证。这个功能的实现也比较简单。内部使用ajv库实现基于JSON Schema的对象校验。

export function validateAPIResponse(
  apiResponse: any, schema: object): boolean {
  const ajvInstance = new Ajv();
  ajvFormats(ajvInstance);
  const validate = ajvInstance.compile(schema);
  const isValid = validate(apiResponse);


  if (!isValid) {
    console.log("Validation errors:", validate.errors);
  }


  return isValid;
}

接下来我们要分析的是如何将TS代码编译成调用fetchCompletion函数的JS代码。

ttypescript 库在@jumploops/magic 内部使用,它允许我们在 tsconfig.json 文件中配置自定义转换器。

在transformer内部,是typescript提供的API,用于解析和操作AST,生成想要的代码。transformer内部的主要处理流程也可以分为3个步骤:

  • 扫描包含 // @magicannotation; 的 AI 函数的源代码;
  • 根据AI函数的返回值类型生成对应的JSON Schema对象;
  • 从AI函数体中提取函数注解,生成调用fetchCompletion函数的代码。

本文的重点不在于如何解析和操作 TypeScript 编译器生成的 AST 对象。如果你有兴趣,可以阅读@jumploops/magic 项目中的transformer.ts 文件。如果您想亲自体验AI功能,可以参考本文示例中package.json和tsconfig.json的配置。

package.json

{
  "name": "magic",
  "scripts": {
    "start": "ttsc && cross-env OPENAI_API_KEY=sk-*** node src/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@jumploops/magic": "^0.0.6",
    "cross-env": "^7.0.3",
    "ts-patch": "^3.0.0",
    "ttypescript": "^1.5.15",
    "typescript": "4.8.2"
  }
}

tsconfig.json文件

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "skipLibCheck": true,
    "plugins": [{ "transform": "@jumploops/magic" }]
  },
  "include": ["src/**/*.ts"],
  "exclude": [ "node_modules"],
}

请注意,聊天完成 API 并不总是以我们期望的格式返回有效的 JSON 对象,因此您在实践中需要添加适当的异常处理逻辑。

目前@jumploops/magic库只提供了简单的示例,尚不支持设置函数的参数。对于这一部分,您可以阅读 Marvin 库中有关 AI Functions 的文档。

如果大语言模型能够按照我们的要求可控地输出结构化数据。那么我们可以做很多事情。

目前很多低代码平台或者RPA(Robotic Process Automation)平台都可以获取对应的JSON Schema对象。

借助 @jumploops/magic 的解决方案,我们可以使低代码平台或 RPA 平台变得更加智能。例如,快速创建表单页面或以自然语言的形式发布各种任务。

最后,我们来总结一下 @jumploops/magic 库背后的工作,它使用 TypeScript 转换器获取函数的返回类型,将类型转换为 JSON Schema 对象,然后替换包含 // @magic 注释函数的源代码 函数的主体,然后调用聊天完成 API 并根据 JSON 架构验证响应。

到这里,今天的这篇文章内容就结束了,希望对你有所帮助。

© 版权声明

相关文章