當(dāng)TS遇上AI,會(huì)發(fā)生什么?
人工智能現(xiàn)在每天都在發(fā)展,大型語(yǔ)言模型變得越來(lái)越強(qiáng)大。工作中使用AI工具幫忙,將大大提高了工作效率,只需敲幾個(gè)字符,按Tab鍵,代碼就智能完成。
除了代碼補(bǔ)全之外,我們還可以讓AI幫助我們自動(dòng)化功能并返回所需的JSON數(shù)據(jù)。
先讓我們看一個(gè)例子:
// 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());
})();
在上面的代碼中,我們定義了一個(gè) getHighestMountain 異步函數(shù)來(lái)獲取世界上最高峰的信息,它的返回值是 Mountain 接口定義的數(shù)據(jù)結(jié)構(gòu)。函數(shù)內(nèi)部沒(méi)有具體的實(shí)現(xiàn),我們只是通過(guò)注釋描述函數(shù)需要做什么。
編譯并執(zhí)行上述代碼后,控制臺(tái)會(huì)輸出如下結(jié)果:
{ name: 'Mount Everest', height: { meters: 8848, feet: 29029 } }
世界最高的山峰是珠穆朗瑪峰,它是喜馬拉雅山脈的主峰,也是世界最高峰,海拔8848.86米,是不是很神奇?
接下來(lái)我就來(lái)揭秘getHighestMountain函數(shù)的秘密。
為了了解 getHighestMountain 異步函數(shù)內(nèi)部做了什么,我們看一下編譯后的 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 庫(kù)中的 fetchCompletion 函數(shù)在 getHighestMountain 函數(shù)內(nèi)部被調(diào)用。
從這個(gè)函數(shù)的參數(shù)中,我們看到了之前TS函數(shù)的函數(shù)注釋?zhuān)送?,我們還看到了一個(gè)包含schema屬性的對(duì)象。該屬性的值為Mountain接口對(duì)應(yīng)的JSON Schema對(duì)象。
接下來(lái)我們重點(diǎn)分析@jumploops/magic庫(kù)中的fetchCompletion函數(shù)。該函數(shù)定義在fetchCompletion.ts文件中,其內(nèi)部處理流程分為3步:
- 組裝 Chat Completions API 所需的提示;
- 調(diào)用Chat Completions API獲取響應(yīng)結(jié)果;
- 解析響應(yīng)結(jié)果并使用 JSON 模式驗(yàn)證響應(yīng)對(duì)象。
// 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中,我們?yōu)锳I設(shè)置了角色,并為它準(zhǔn)備了一些例子來(lái)引導(dǎo)它返回有效的JSON格式。
調(diào)用Chat Completions API獲取響應(yīng)結(jié)果,直接使用openai庫(kù)提供的createChatCompletion API。
解析得到響應(yīng)結(jié)果后,會(huì)調(diào)用validateAPIResponse函數(shù)對(duì)響應(yīng)對(duì)象進(jìn)行驗(yàn)證。這個(gè)功能的實(shí)現(xiàn)也比較簡(jiǎn)單。內(nèi)部使用ajv庫(kù)實(shí)現(xiàn)基于JSON Schema的對(duì)象校驗(yàn)。
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;
}
接下來(lái)我們要分析的是如何將TS代碼編譯成調(diào)用fetchCompletion函數(shù)的JS代碼。
ttypescript 庫(kù)在@jumploops/magic 內(nèi)部使用,它允許我們?cè)?tsconfig.json 文件中配置自定義轉(zhuǎn)換器。
在transformer內(nèi)部,是typescript提供的API,用于解析和操作AST,生成想要的代碼。transformer內(nèi)部的主要處理流程也可以分為3個(gè)步驟:
- 掃描包含 // @magicannotation; 的 AI 函數(shù)的源代碼;
- 根據(jù)AI函數(shù)的返回值類(lèi)型生成對(duì)應(yīng)的JSON Schema對(duì)象;
- 從AI函數(shù)體中提取函數(shù)注解,生成調(diào)用fetchCompletion函數(shù)的代碼。
本文的重點(diǎn)不在于如何解析和操作 TypeScript 編譯器生成的 AST 對(duì)象。如果你有興趣,可以閱讀@jumploops/magic 項(xiàng)目中的transformer.ts 文件。如果您想親自體驗(yàn)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"],
}
請(qǐng)注意,聊天完成 API 并不總是以我們期望的格式返回有效的 JSON 對(duì)象,因此您在實(shí)踐中需要添加適當(dāng)?shù)漠惓L幚磉壿嫛?/span>
目前@jumploops/magic庫(kù)只提供了簡(jiǎn)單的示例,尚不支持設(shè)置函數(shù)的參數(shù)。對(duì)于這一部分,您可以閱讀 Marvin 庫(kù)中有關(guān) AI Functions 的文檔。
如果大語(yǔ)言模型能夠按照我們的要求可控地輸出結(jié)構(gòu)化數(shù)據(jù)。那么我們可以做很多事情。
目前很多低代碼平臺(tái)或者RPA(Robotic Process Automation)平臺(tái)都可以獲取對(duì)應(yīng)的JSON Schema對(duì)象。
借助 @jumploops/magic 的解決方案,我們可以使低代碼平臺(tái)或 RPA 平臺(tái)變得更加智能。例如,快速創(chuàng)建表單頁(yè)面或以自然語(yǔ)言的形式發(fā)布各種任務(wù)。
最后,我們來(lái)總結(jié)一下 @jumploops/magic 庫(kù)背后的工作,它使用 TypeScript 轉(zhuǎn)換器獲取函數(shù)的返回類(lèi)型,將類(lèi)型轉(zhuǎn)換為 JSON Schema 對(duì)象,然后替換包含 // @magic 注釋函數(shù)的源代碼 函數(shù)的主體,然后調(diào)用聊天完成 API 并根據(jù) JSON 架構(gòu)驗(yàn)證響應(yīng)。
到這里,今天的這篇文章內(nèi)容就結(jié)束了,希望對(duì)你有所幫助。