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

人工智能3年前发布 zjch2
46 0 0

人工智能现在每天都在发展,大型语言模型变得越来越强大。工作中使用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 架构验证响应。

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

© 版权声明

相关文章