Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 8f801f6

Browse files
authored
Merge pull request #6 from wrtnio/features/cli
CLI + README enhancement
2 parents 5ea76c0 + d05fa20 commit 8f801f6

File tree

9 files changed

+2033
-14
lines changed

9 files changed

+2033
-14
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ on:
66
- 'test/**'
77
- 'package.json'
88
- 'tsconfig.json'
9+
- .github/workflows/build.yml
910
pull_request:
1011
paths:
1112
- 'src/**'
1213
- 'test/**'
1314
- 'package.json'
1415
- 'tsconfig.json'
15-
16+
- .github/workflows/build.yml
1617
jobs:
1718
Ubuntu:
1819
runs-on: ubuntu-latest
@@ -21,12 +22,8 @@ jobs:
2122
- uses: actions/setup-node@v4
2223
with:
2324
node-version: 20.x
24-
- uses: pnpm/action-setup@v2
25-
with:
26-
version: 8
27-
2825
- name: Install dependencies
29-
run: pnpm install
26+
run: npm install
3027
- name: Build
3128
run: npm run build
3229
- name: Test

README.md

Lines changed: 232 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ main().catch(console.error);
6565
## Features
6666
About supported features, please read description comments of each component.
6767

68-
I'm preparing documentation and playground website of `@wrtnio/openai-function-schema` features. Until that, please read below components' description comments. Even though you have to read source code of each component, but description comments would satisfy you.
68+
I'm preparing documentation and playground website of `@wrtnio/openai-function-schema` features. Until that, please read below components' description comments. Even though you have to read source code of each component, but description comments of them may satisfy you.
6969

7070
- Schema Definitions
7171
- [`IOpenAiDocument`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiDocument.ts): OpenAI function metadata collection with options
@@ -76,3 +76,234 @@ I'm preparing documentation and playground website of `@wrtnio/openai-function-s
7676
- [`OpenAiFetcher`](https://github.com/wrtnio/openai-function-schema/blob/master/src/OpenAiFetcher.ts): Function call executor with `IOpenAiFunction`
7777
- [`OpenAiDataCombiner`](https://github.com/wrtnio/openai-function-schema/blob/master/src/OpenAiDataCombiner.ts): Data combiner for LLM function call with human composed data
7878
- [`OpenAiTypeChecker`](https://github.com/wrtnio/openai-function-schema/blob/master/src/OpenAiTypeChecker.ts): Type checker for `IOpenAiSchema`
79+
80+
### Command Line Interface
81+
```bash
82+
########
83+
# LAUNCH CLI
84+
########
85+
# PRIOR TO NODE V20
86+
npm install -g @wrtnio/openai-function-schema
87+
npx wofs
88+
89+
# SINCE NODE V20
90+
npx @wrtnio/openai-function-schema
91+
92+
########
93+
# PROMPT
94+
########
95+
--------------------------------------------------------
96+
Swagger to OpenAI Function Call Schema Converter
97+
--------------------------------------------------------
98+
? Swagger file path: test/swagger.json
99+
? OpenAI Function Call Schema file path: test/plain.json
100+
? Whether to wrap parameters into an object with keyword or not: No
101+
```
102+
103+
Convert swagger to OpenAI function schema file by a CLI command.
104+
105+
If you run `npx @wrtnio/openai-function-schema` (or `npx wofs` after global setup), the CLI (Command Line Interface) will inquiry those arguments. After you fill all of them, the OpenAI fuction call schema file of [`IOpenAiDocument`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiDocument.ts) type would be created to the target location.
106+
107+
If you want to specify arguments without prompting, you can fill them like below:
108+
109+
```bash
110+
# PRIOR TO NODE V20
111+
npm install -g @wrtnio/openai-function-schema
112+
npx wofs --input swagger.json --output openai.json --keyword false
113+
114+
# SINCE NODE V20
115+
npx @wrtnio/openai-function-schema
116+
--input swagger.json
117+
--output openai.json
118+
--keyword false
119+
```
120+
121+
122+
123+
124+
### Library API
125+
If you want to utilize `@wrtnio/openai-function-schema` in the API level, you should start from composing [`IOpenAiDocument`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiDocument.ts) through `OpenAiComposer.document()` method.
126+
127+
After composing the [`IOpenAiDocument`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiDocument.ts) data, you may provide the nested [`IOpenAiFunction`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiFunction.ts) instances to the OpenAI, and the OpenAI may compose the arguments by its function calling feature. With the OpenAI automatically composed arguments, you can execute the function call by `OpenAiFetcher.execute()` method.
128+
129+
Here is the example code composing and executing the [`IOpenAiFunction`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiFunction.ts).
130+
131+
- Test Function: [test_fetcher_positional_bbs_article_update.ts](https://github.com/wrtnio/openai-function-schema/blob/main/test/features/fetcher/positional/test_fetcher_positional_bbs_article_update.ts)
132+
- Backend Server Code: [BbsArticlesController.ts](https://github.com/wrtnio/openai-function-schema/blob/main/test/controllers/BbsArticlesController.ts)
133+
134+
```typescript
135+
import {
136+
IOpenAiDocument,
137+
IOpenAiFunction,
138+
OpenAiComposer,
139+
OpenAiFetcher,
140+
} from "@wrtnio/openai-function-schema";
141+
import fs from "fs";
142+
import typia from "typia";
143+
import { v4 } from "uuid";
144+
145+
import { IBbsArticle } from "../../../api/structures/IBbsArticle";
146+
147+
const main = async (): Promise<void> => {
148+
// COMPOSE OPENAI FUNCTION CALL SCHEMAS
149+
const swagger = JSON.parse(
150+
await fs.promises.readFile("swagger.json", "utf8"),
151+
);
152+
const document: IOpenAiDocument = OpenAiComposer.document({
153+
swagger
154+
});
155+
156+
// EXECUTE OPENAI FUNCTION CALL
157+
const func: IOpenAiFunction = document.functions.find(
158+
(f) => f.method === "put" && f.path === "/bbs/articles",
159+
)!;
160+
const article: IBbsArticle = await OpenAiFetcher.execute({
161+
document,
162+
function: func,
163+
connection: { host: "http://localhost:3000" },
164+
arguments: [
165+
// imagine that arguments are composed by OpenAI
166+
v4(),
167+
typia.random<IBbsArticle.ICreate>(),
168+
],
169+
});
170+
typia.assert(article);
171+
};
172+
main().catch(console.error);
173+
```
174+
175+
By the way, above example code's target operation function has multiple parameters. You know what? If you configure a function to have only one parameter by wrapping into one object type, OpenAI function calling feature constructs arguments a little bit efficiently than multiple parameters case.
176+
177+
Such only one object typed parameter is called `keyword parameter`, and `@wrtnio/openai-function-schema` supports such keyword parameterized function schemas. When composing [`IOpenAiDocument`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiDocument.ts) by `OpenAiComposer.document()` method, configures `option.keyword` to be `true`, then every [`IOpenAiFunction`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiFunction.ts) instances would be keyword parameterized. Also, `OpenAiFetcher` understands the keyword parameterized function specification, so that performs proper execution by automatic decomposing the arguments.
178+
179+
Here is the example code of keyword parameterizing.
180+
181+
- Test Function: [test_fetcher_keyword_bbs_article_update.ts](https://github.com/wrtnio/openai-function-schema/blob/main/test/features/fetcher/keyword/test_fetcher_keyword_bbs_article_update.ts)
182+
- Backend Server Code: [BbsArticlesController.ts](https://github.com/wrtnio/openai-function-schema/blob/main/test/controllers/BbsArticlesController.ts)
183+
184+
```typescript
185+
import {
186+
IOpenAiDocument,
187+
IOpenAiFunction,
188+
OpenAiComposer,
189+
OpenAiFetcher,
190+
} from "@wrtnio/openai-function-schema";
191+
import fs from "fs";
192+
import typia from "typia";
193+
import { v4 } from "uuid";
194+
195+
import { IBbsArticle } from "../../../api/structures/IBbsArticle";
196+
197+
const main = async (): Promise<void> => {
198+
// COMPOSE OPENAI FUNCTION CALL SCHEMAS
199+
const swagger = JSON.parse(
200+
await fs.promises.readFile("swagger.json", "utf8"),
201+
);
202+
const document: IOpenAiDocument = OpenAiComposer.document({
203+
swagger,
204+
options: {
205+
keyword: true, // keyword parameterizing
206+
}
207+
});
208+
209+
// EXECUTE OPENAI FUNCTION CALL
210+
const func: IOpenAiFunction = document.functions.find(
211+
(f) => f.method === "put" && f.path === "/bbs/articles",
212+
)!;
213+
const article: IBbsArticle = await OpenAiFetcher.execute({
214+
document,
215+
function: func,
216+
connection: { host: "http://localhost:3000" },
217+
arguments: [
218+
// imagine that argument is composed by OpenAI
219+
{
220+
id: v4(),
221+
body: typia.random<IBbsArticle.ICreate>(),
222+
},
223+
],
224+
});
225+
typia.assert(article);
226+
};
227+
main().catch(console.error);
228+
```
229+
230+
At last, there can be some special API operation that some arguments must be composed by user, not by LLM (Large Language Model). For example, if an API operation requires file uploading or secret key identifier, it must be composed by user manually in the frontend application side.
231+
232+
For such case, `@wrtnio/openai-function-schema` supports special option [`IOpenAiDocument.IOptions.separate`](https://github.com/wrtnio/openai-function-schema/blob/master/src/structures/IOpenAiDocument.ts). If you configure the callback function, it would be utilized for determining whether the value must be composed by user or not. When the arguments are composed by both user and LLM sides, you can combine them into one through `OpenAiDataComposer.parameters()` method, so that you can still execute the function calling with `OpenAiFetcher.execute()` method.
233+
234+
Here is the example code of such special case:
235+
236+
- Test Function: [test_combiner_keyword_parameters_query.ts](https://github.com/wrtnio/openai-function-schema/blob/main/test/features/combiner/test_combiner_keyword_parameters_query.ts)
237+
- Backend Server Code: [MembershipController.ts](https://github.com/wrtnio/openai-function-schema/blob/main/test/controllers/MembershipController.ts)
238+
239+
```typescript
240+
import {
241+
IOpenAiDocument,
242+
IOpenAiFunction,
243+
IOpenAiSchema,
244+
OpenAiComposer,
245+
OpenAiDataCombiner,
246+
OpenAiFetcher,
247+
OpenAiTypeChecker,
248+
} from "@wrtnio/openai-function-schema";
249+
import fs from "fs";
250+
import typia from "typia";
251+
252+
import { IMembership } from "../../api/structures/IMembership";
253+
254+
const main = async (): Promise<void> => {
255+
// COMPOSE OPENAI FUNCTION CALL SCHEMAS
256+
const swagger = JSON.parse(
257+
await fs.promises.readFile("swagger.json", "utf8"),
258+
);
259+
const document: IOpenAiDocument = OpenAiComposer.document({
260+
swagger,
261+
options: {
262+
keyword: true,
263+
separate: (schema: IOpenAiSchema) =>
264+
OpenAiTypeChecker.isString(schema) &&
265+
(schema["x-wrtn-secret-key"] !== undefined ||
266+
schema["contentMediaType"] !== undefined),
267+
},
268+
});
269+
270+
// EXECUTE OPENAI FUNCTION CALL
271+
const func: IOpenAiFunction = document.functions.find(
272+
(f) => f.method === "patch" && f.path === "/membership/change",
273+
)!;
274+
const membership: IMembership = await OpenAiFetcher.execute({
275+
document,
276+
function: func,
277+
connection: { host: "http://localhost:3000" },
278+
arguments: OpenAiDataCombiner.parameters({
279+
function: func,
280+
llm: [
281+
// imagine that below argument is composed by OpenAI
282+
{
283+
body: {
284+
name: "Wrtn Technologies",
285+
email: "master@wrtn.io",
286+
password: "1234",
287+
age: 20,
288+
gender: 1,
289+
},
290+
},
291+
],
292+
human: [
293+
// imagine that below argument is composed by human
294+
{
295+
query: {
296+
secret: "something",
297+
},
298+
body: {
299+
secretKey: "something",
300+
picture: "https://wrtn.io/logo.png",
301+
},
302+
},
303+
],
304+
}),
305+
});
306+
typia.assert(membership);
307+
};
308+
main().catch(console.error);
309+
```

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
{
22
"name": "@wrtnio/openai-function-schema",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "OpenAI LLM function schema from OpenAPI (Swagger) document",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",
77
"module": "lib/index.mjs",
8+
"bin": {
9+
"wofs": "lib/executable/wofs.js"
10+
},
811
"scripts": {
912
"prepare": "ts-patch install",
1013
"build": "npm run build:main && npm run build:test",
1114
"build:main": "rimraf lib && tsc && rollup -c",
1215
"build:test": "rimraf bin && tsc -p test/tsconfig.json",
1316
"dev": "npm run build:test -- --watch",
14-
"test": "node bin/test"
17+
"test": "npm run test:api && npm run test:cli",
18+
"test:api": "node bin/test",
19+
"test:cli": "node bin/src/executable/wofs.js --input test/swagger.json --output test/plain.json --keyword false"
1520
},
1621
"keywords": [
1722
"openai",
@@ -30,7 +35,10 @@
3035
"license": "ISC",
3136
"dependencies": {
3237
"@nestia/fetcher": "^3.4.1",
33-
"@samchon/openapi": "^0.3.0"
38+
"@samchon/openapi": "^0.3.0",
39+
"commander": "^10.0.0",
40+
"inquirer": "^8.2.5",
41+
"typia": "^6.4.0"
3442
},
3543
"devDependencies": {
3644
"@nestia/core": "^3.4.1",
@@ -42,6 +50,7 @@
4250
"@rollup/plugin-terser": "^0.4.4",
4351
"@rollup/plugin-typescript": "^11.1.6",
4452
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
53+
"@types/inquirer": "^8.2.5",
4554
"@types/node": "^20.14.9",
4655
"@types/uuid": "^10.0.0",
4756
"nestia": "^5.3.1",
@@ -51,7 +60,6 @@
5160
"ts-patch": "^3.2.1",
5261
"typescript": "5.5.2",
5362
"typescript-transform-paths": "^3.4.7",
54-
"typia": "^6.4.0",
5563
"uuid": "^10.0.0"
5664
},
5765
"files": [

src/OpenAiComposer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "@samchon/openapi";
99
import { OpenApiTypeChecker } from "@samchon/openapi/lib/internal/OpenApiTypeChecker";
1010
import { OpenApiV3Downgrader } from "@samchon/openapi/lib/internal/OpenApiV3Downgrader";
11+
import typia from "typia";
1112

1213
import { OpenAiSchemaSeparator } from "./internal/OpenAiSchemaSeparator";
1314
import { IOpenAiSchema, ISwaggerOperation } from "./module";
@@ -92,7 +93,8 @@ export namespace OpenAiComposer {
9293
*/
9394
export const document = (props: IProps): IOpenAiDocument => {
9495
// LIST UP ARGUMENTS
95-
const swagger: ISwagger = OpenApi.convert<any, any>(props.swagger) as any;
96+
typia.assert(props);
97+
const swagger: ISwagger = OpenApi.convert(props.swagger);
9698
const options: IOpenAiDocument.IOptions = {
9799
keyword: props.options?.keyword ?? false,
98100
separate: props.options?.separate ?? null,

0 commit comments

Comments
 (0)