Skip to content

Commit

Permalink
Generate the pointer array configuration through gpt (#63)
Browse files Browse the repository at this point in the history
Try using gpt as a renderer to help us alleviate some manual work.
  • Loading branch information
littleGnAl authored Mar 21, 2024
1 parent 5b27958 commit b895d4f
Show file tree
Hide file tree
Showing 8 changed files with 628 additions and 2 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
164 changes: 164 additions & 0 deletions configs/rtc/pointer_marker.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { CXXTYPE } from '@agoraio-extensions/cxx-parser';

module.exports = {
markers: [
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'LiveTranscoding',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'transcodingUsers',
lengthName: 'userCount',
},
{
ptrName: 'watermark',
lengthName: 'watermarkCount',
},
{
ptrName: 'backgroundImage',
lengthName: 'backgroundImageCount',
},
{
ptrName: 'advancedFeatures',
lengthName: 'advancedFeatureCount',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'ScreenCaptureParameters',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'excludeWindowList',
lengthName: 'excludeWindowCount',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'ChannelMediaRelayConfiguration',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'destInfos',
lengthName: 'destCount',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'LocalAccessPointConfiguration',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'ipList',
lengthName: 'ipListSize',
},
{
ptrName: 'domainList',
lengthName: 'domainListSize',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'ContentInspectConfig',
namespaces: ['agora', 'media'],
},
pointerArrayNameMappings: [
{
ptrName: 'modules',
lengthName: 'moduleCount',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'AudioSpectrumData',
namespaces: ['agora', 'media'],
},
pointerArrayNameMappings: [
{
ptrName: 'audioSpectrumData',
lengthName: 'dataLength',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'LogConfig',
namespaces: ['agora', 'commons'],
},
pointerArrayNameMappings: [
{
ptrName: 'filePath',
lengthName: 'fileSizeInKB',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'InputSeiData',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'private_data',
lengthName: 'data_size',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'Music',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'lyricList',
lengthName: 'lyricCount',
},
{
ptrName: 'climaxSegmentList',
lengthName: 'climaxSegmentCount',
},
{
ptrName: 'mvPropertyList',
lengthName: 'mvPropertyCount',
},
],
},
{
node: {
__TYPE: CXXTYPE.Struct,
name: 'VideoCompositingLayout',
namespaces: ['agora', 'rtc'],
},
pointerArrayNameMappings: [
{
ptrName: 'regions',
lengthName: 'regionCount',
},
{
ptrName: 'appData',
lengthName: 'appDataLength',
},
],
},
],
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@
"dependencies": {
"@agoraio-extensions/cxx-parser": "[email protected]:AgoraIO-Extensions/terra.git#head=main&workspace=cxx-parser",
"@agoraio-extensions/terra-core": "[email protected]:AgoraIO-Extensions/terra.git#head=main&workspace=terra-core",
"mustache": "^4.2.0"
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"openai": "^4.29.1"
},
"devDependencies": {
"@types/jest": "^29.5.1",
"@types/lodash": "^4.17.0",
"@types/mustache": "^4.2.2",
"@types/node": "^20.5.9",
"@typescript-eslint/eslint-plugin": "^5.30.5",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './renderers/mustache_renderer';
export * from './renderers/iris_doc_renderer';
export * from './renderers/pointer_marker_gpt_renderer';

export * from './parsers';
export * from './utils';
145 changes: 145 additions & 0 deletions src/renderers/pointer_marker_gpt_renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { execSync } from 'child_process';
import * as fs from 'fs';
import path from 'path';
import _ from 'lodash';

import {
ParseResult,
RenderResult,
TerraContext,
} from '@agoraio-extensions/terra-core';
import { askGPT } from '../utils/gpt_utils';
import { PointerArrayNameMapping, PointerMarkerParserConfigMarker } from '../parsers/pointer_marker_parser';
import { CXXFile, SimpleTypeKind, Struct } from '@agoraio-extensions/cxx-parser';

export interface PointerMarkerGPTRenderer {
configPath?: string
}

export function PointerMarkerGPTRenderer(
terraContext: TerraContext,
args?: PointerMarkerGPTRenderer,
parseResult?: ParseResult
): RenderResult[] {
processGPT(parseResult!, args?.configPath);
return [];
}

const prompt = `
You are a C++ Code Inspector. Your task is to identify "pointer field-length field" pairs within a given C++ struct.
Exclude any pair where the pointer field's name is "buffer" or the pointer field type is "uint8_t" pointer. Additionally, disregard any pointer without a direct length field counterpart.
Your output should be in JSON format, returning an empty array [] if no valid pairs are found, or [{"ptrName": "pointer_name", "lengthName": "length_field_name"}] for each valid pair found.
The response MUST contains only the JSON result without any additional explanations.
Given struct:
\`\`\`c++
{{ STRUCT_SOURCE }}
\`\`\`
`;

const defualtConfigPath = path.resolve(`${__dirname}/../../configs/rtc/pointer_marker.config.ts`);
async function processGPT(parseResult: ParseResult, configPath?: string) {
let originalConfigPath = configPath ?? defualtConfigPath;
let originalMarkers = require(originalConfigPath).markers as PointerMarkerParserConfigMarker[];

let structs = parseResult.nodes
.flatMap((node) => (node as CXXFile).nodes)
.filter((node) => node.isStruct())
.filter((node) => {
return node.asStruct()
.member_variables
.find((member) => member.type.kind === SimpleTypeKind.pointer_t) !== undefined;
});

let markers: string[] = [];

for (let st of structs) {
let struct = st.asStruct();
let structSource = structToSource(struct);
let promptWithStruct = prompt
.replace('{{ STRUCT_NAME }}', struct.name)
.replace('{{ STRUCT_SOURCE }}', structSource)
.trim();
let res = await askGPT(promptWithStruct);
if (res.length > 0) {
let jsonArray = Object.values(JSON.parse(res));
if (jsonArray.length === 0) {
continue;
}

// let newJsonArray = jsonArray as PointerArrayNameMapping[];
let newJsonArray: PointerArrayNameMapping[] = [];
let originalMarker = originalMarkers.find((entry: any) => _.isMatch(struct, entry.node));
if (originalMarker) {
for (let om of (jsonArray as PointerArrayNameMapping[])) {
if (om.lengthName.length === 0) {
continue;
}
let found = originalMarker.pointerArrayNameMappings?.find((entry) => entry.ptrName === om.ptrName);
// Only add the ptrName if it's not found in the original marker
let toAdd = found ? found : om;
newJsonArray.push(toAdd);
}
}

if (newJsonArray.length > 0) {
let pointerArrayNameMappings = newJsonArray.map((entry: any) => {
return `
{
ptrName: "${entry.ptrName}",
lengthName: "${entry.lengthName}",
}`.trim();
});

let marker = `
{
node: {
__TYPE: CXXTYPE.Struct,
name: "${struct.name}",
namespaces: [${struct.namespaces.map((it) => `"${it}"`).join(',')}],
},
pointerArrayNameMappings: [
${pointerArrayNameMappings.join(',\n')}
],
}`.trim();
markers.push(marker);
}
}
}
console.log(markers);

let output = `
import { CXXTYPE } from "@agoraio-extensions/cxx-parser";
module.exports = {
markers: [
${markers.join(',\n')}
],
};
`.trim();

fs.writeFileSync(originalConfigPath, output);

// Reformat the file
execSync(`yarn prettier ${originalConfigPath} --write`, {
cwd: path.resolve(__dirname, '../../'),
});
}

function structToSource(struct: Struct): string {
let structName = struct.name;
let structContent = struct.member_variables.map((member) => {
let memberName = member.name;
let memberType = member.type.source;
let memberComment = member.comment.split('\n').map((line) => `/// ${line}`).join('\n');
return `
${memberComment}
${memberType} ${memberName};`.trim();
}).join('\n\n');

return `
struct ${structName} {
${structContent}
};`.trim();
}

48 changes: 48 additions & 0 deletions src/utils/gpt_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import OpenAI from "openai";

let _openAIClient: OpenAI | undefined = undefined;
function openAIClient(): OpenAI {
if (_openAIClient === undefined) {
_openAIClient = new OpenAI();
}
return _openAIClient;
}

/// Make sure you add the following environment variables before you call this function
/// - OPENAI_API_KEY
/// - OPENAI_BASE_URL
export async function askGPT(prompt: string): Promise<string> {
console.log(`prompt:`);
console.log(prompt);

const completion: OpenAI.Chat.ChatCompletion = (await openAIClient().chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
}));

let response: any | undefined = undefined;
try {
// We can only make a synchronous call inside terra at this time, but in this way the completions API returns a string, so we need to
// do some tricky thing here.
if (completion !== undefined && typeof completion === 'string') {
let completionStr = completion as string;
if (completionStr.length > 0) {
response = JSON.parse(completionStr);
}
} else if (typeof completion === 'object' && completion !== null) {
response = completion;
} else {
console.log('Param is neither a string nor an object');
}
} catch (error) {
console.error(error);
}

let res = response?.choices[0]?.message?.content ?? '';

console.log(`response:`);
console.log(res);

return res;
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './obj_utils';
export * from './iris_utils';
export * from './gpt_utils';

import { SimpleTypeKind, Variable } from '@agoraio-extensions/cxx-parser';

Expand Down
Loading

0 comments on commit b895d4f

Please sign in to comment.