人工智能现在每天都在发展,大型语言模型变得越来越强大。工作中使用AI工具帮忙,将大大提高了工作效率,只需敲几个字符,按Tab键,代码就智能完成。
除了代码补全之外,我们还可以让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 架构验证响应。
到这里,今天的这篇文章内容就结束了,希望对你有所帮助。