From 9ed145fa774486205abb969394195033422498ad Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Tue, 14 Jan 2025 01:44:36 +0900 Subject: [PATCH 1/2] Develop samchon/openapi#124 and samchon/openapi#125. As OpenAI (ChatGPT) restricts function name and description lengths (64 and 1,024), `@samchon/openapi`'s `HttpLlm.application()` function converting OpenAPI document to LLM function calling application has adopted it. Therefore, this PR makes `typia` to occur compilation error when 64 length over function name comes, or 1,024 length over descripted function comes. Also, `typia` has allowed empty parameterized functions in the `typia.llm.application()` function. --- benchmark/package.json | 2 +- package.json | 8 +-- .../json/JsonApplicationProgrammer.ts | 54 ++++++++-------- .../llm/LlmApplicationOfValidateProgrammer.ts | 18 +++++- .../llm/LlmApplicationProgrammer.ts | 62 +++++++++++++++---- test-error/package.json | 2 +- test-error/src/llm/llm.application.length.ts | 19 ++++++ test-esm/package.json | 2 +- test/package.json | 2 +- test/src/debug/llm.ts | 10 +++ website/package.json | 2 +- 11 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 test-error/src/llm/llm.application.length.ts create mode 100644 test/src/debug/llm.ts diff --git a/benchmark/package.json b/benchmark/package.json index 2cb6386efe..ef3c13aa74 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -62,7 +62,7 @@ "ts-node": "^10.9.1", "ts-patch": "^3.3.0", "tstl": "^3.0.0", - "typescript": "~5.7.2", + "typescript": "~5.7.3", "uuid": "^8.3.2", "zod": "^3.19.1" }, diff --git a/package.json b/package.json index b588e2b0ed..3ae4e61e7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typia", - "version": "7.5.1", + "version": "7.6.0", "description": "Superfast runtime validators with only one line", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -41,7 +41,7 @@ }, "homepage": "https://typia.io", "dependencies": { - "@samchon/openapi": "^2.3.1", + "@samchon/openapi": "^2.4.0", "commander": "^10.0.0", "comment-json": "^4.2.3", "inquirer": "^8.2.5", @@ -50,7 +50,7 @@ }, "peerDependencies": { "typescript": ">=4.8.0 <5.8.0", - "@samchon/openapi": ">=2.3.0 <3.0.0" + "@samchon/openapi": ">=2.4.0 <3.0.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^26.0.1", @@ -68,7 +68,7 @@ "rollup": "^4.18.0", "suppress-warnings": "^1.0.2", "ts-node": "^10.9.2", - "typescript": "~5.7.2" + "typescript": "~5.7.3" }, "stackblitz": { "startCommand": "npm install && npm run test" diff --git a/src/programmers/json/JsonApplicationProgrammer.ts b/src/programmers/json/JsonApplicationProgrammer.ts index 3921708bac..ea9e108660 100644 --- a/src/programmers/json/JsonApplicationProgrammer.ts +++ b/src/programmers/json/JsonApplicationProgrammer.ts @@ -130,6 +130,33 @@ export namespace JsonApplicationProgrammer { }; }; + export const writeDescription = (props: { + description: string | null; + jsDocTags: IJsDocTagInfo[]; + kind: Kind; + }): Kind extends "summary" + ? { summary?: string; description?: string } + : { title?: string; description?: string } => { + const title: string | undefined = (() => { + const [explicit] = getJsDocTexts({ + jsDocTags: props.jsDocTags, + name: props.kind, + }); + if (explicit?.length) return explicit; + else if (!props.description?.length) return undefined; + + const index: number = props.description.indexOf("\n"); + const top: string = ( + index === -1 ? props.description : props.description.substring(0, index) + ).trim(); + return top.endsWith(".") ? top.substring(0, top.length - 1) : undefined; + })(); + return { + [props.kind]: title, + description: props.description?.length ? props.description : undefined, + } as any; + }; + const collectFunction = (props: { version: Version; name: string; @@ -217,33 +244,6 @@ export namespace JsonApplicationProgrammer { }; } -const writeDescription = (props: { - description: string | null; - jsDocTags: IJsDocTagInfo[]; - kind: Kind; -}): Kind extends "summary" - ? { summary?: string; description?: string } - : { title?: string; description?: string } => { - const title: string | undefined = (() => { - const [explicit] = getJsDocTexts({ - jsDocTags: props.jsDocTags, - name: props.kind, - }); - if (explicit?.length) return explicit; - else if (!props.description?.length) return undefined; - - const index: number = props.description.indexOf("\n"); - const top: string = ( - index === -1 ? props.description : props.description.substring(0, index) - ).trim(); - return top.endsWith(".") ? top.substring(0, top.length - 1) : undefined; - })(); - return { - [props.kind]: title, - description: props.description?.length ? props.description : undefined, - } as any; -}; - const writeDescriptionFromJsDocTag = (props: { jsDocTags: IJsDocTagInfo[]; name: string; diff --git a/src/programmers/llm/LlmApplicationOfValidateProgrammer.ts b/src/programmers/llm/LlmApplicationOfValidateProgrammer.ts index 18f836459f..c9faed867a 100644 --- a/src/programmers/llm/LlmApplicationOfValidateProgrammer.ts +++ b/src/programmers/llm/LlmApplicationOfValidateProgrammer.ts @@ -1,6 +1,8 @@ import { ILlmApplication, ILlmSchema } from "@samchon/openapi"; import ts from "typescript"; +import { TypeFactory } from "../../factories/TypeFactory"; + import { ILlmApplicationOfValidate } from "../../schemas/llm/ILlmApplicationOfValidate"; import { Metadata } from "../../schemas/metadata/Metadata"; import { MetadataParameter } from "../../schemas/metadata/MetadataParameter"; @@ -55,7 +57,7 @@ export namespace LlmApplicationOfValidateProgrammer { modulo: props.modulo, className: props.name, name: func.name, - parameter: parameters[func.name]!, + parameter: parameters[func.name] ?? null, }), })), }; @@ -64,10 +66,22 @@ export namespace LlmApplicationOfValidateProgrammer { const writeValidadtor = (props: { context: ITypiaContext; modulo: ts.LeftHandSideExpression; - parameter: MetadataParameter; + parameter: MetadataParameter | null; name: string; className?: string; }): ((props: object) => IValidation) => { + if (props.parameter === null) + return ValidateProgrammer.write({ + ...props, + type: props.context.checker.getTypeFromTypeNode( + TypeFactory.keyword("any"), + ), + config: { + equals: false, + }, + name: undefined, + }) as any; + const type = props.parameter.tsType; if (type === undefined) // unreachable diff --git a/src/programmers/llm/LlmApplicationProgrammer.ts b/src/programmers/llm/LlmApplicationProgrammer.ts index 4403b0d97a..aeace33f00 100644 --- a/src/programmers/llm/LlmApplicationProgrammer.ts +++ b/src/programmers/llm/LlmApplicationProgrammer.ts @@ -42,12 +42,12 @@ export namespace LlmApplicationProgrammer { else return LlmSchemaProgrammer.validate(props)(metadata); const output: string[] = []; - const valid: boolean = + const validity: boolean = metadata.size() === 1 && metadata.objects.length === 1 && metadata.isRequired() === true && metadata.nullable === false; - if (valid === false) + if (validity === false) output.push( "LLM application's generic arugment must be a class/interface type.", ); @@ -60,23 +60,40 @@ export namespace LlmApplicationProgrammer { ); let least: boolean = false; for (const p of object.properties) { + const name: string = JSON.stringify(p.key.getSoleLiteral()!); const value: Metadata = p.value; if (value.functions.length) { least ||= true; - if (valid === false) { + if (validity === false) { if (value.functions.length !== 1 || value.size() !== 1) output.push( - "LLM application's function type does not allow union type.", + `LLM application's function (${name}) type does not allow union type.`, ); if (value.isRequired() === false) output.push( - "LLM application's function type must be required.", + `LLM application's function (${name}) type must be required.`, ); if (value.nullable === true) output.push( - "LLM application's function type must not be nullable.", + `LLM application's function (${name}) type must not be nullable.`, ); } + + const description: string | undefined = concatDescription( + JsonApplicationProgrammer.writeDescription({ + description: + p.description ?? + p.jsDocTags.find((tag) => tag.name === "description") + ?.text?.[0]?.text ?? + null, + jsDocTags: p.jsDocTags, + kind: "summary", + }), + ); + if (description !== undefined && description.length > 1_024) + output.push( + `LLM application's function (${name}) description must not exceed 1,024 characters.`, + ); } } if (least === false) @@ -96,10 +113,14 @@ export namespace LlmApplicationProgrammer { `${prefix}'s return type must not be union type with undefined.`, ); if (/^[0-9]/.test(name[0] ?? "") === true) - output.push(`name must not start with a number.`); + output.push(`${prefix} name must not start with a number.`); if (/^[a-zA-Z0-9_-]+$/.test(name) === false) - output.push(`name must be alphanumeric with underscore or hypen.`); - if (func.parameters.length !== 1) + output.push( + `${prefix} name must be alphanumeric with underscore or hypen.`, + ); + if (name.length > 64) + output.push(`${prefix} name must not exceed 64 characters.`); + if (func.parameters.length !== 0 && func.parameters.length !== 1) output.push(`${prefix} must have a single parameter.`); if (func.parameters.length !== 0) { const type: Metadata = func.parameters[0]!.type; @@ -233,9 +254,12 @@ export namespace LlmApplicationProgrammer { errors: string[]; accessor: string; }): ILlmSchema.ModelParameters[Model] | null => { - const schema = props.function.parameters[0]?.schema; - if (!schema) return null; - + const schema = props.function.parameters[0]?.schema ?? { + type: "object", + properties: {}, + additionalProperties: false, + required: [], + }; const result: IResult< ILlmSchema.ModelParameters[Model], IOpenApiSchemaError @@ -286,4 +310,18 @@ export namespace LlmApplicationProgrammer { } return result.value; }; + + const concatDescription = (p: { + summary?: string | undefined; + description?: string | undefined; + }): string | undefined => { + if (!p.summary?.length || !p.description?.length) + return p.summary ?? p.description; + const summary: string = p.summary.endsWith(".") + ? p.summary.slice(0, -1) + : p.summary; + return p.description.startsWith(summary) + ? p.description + : summary + ".\n\n" + p.description; + }; } diff --git a/test-error/package.json b/test-error/package.json index b83e9da630..9d39cddfca 100644 --- a/test-error/package.json +++ b/test-error/package.json @@ -29,7 +29,7 @@ "devDependencies": { "rimraf": "^5.0.5", "ts-patch": "^3.3.0", - "typescript": "~5.7.2" + "typescript": "~5.7.3" }, "dependencies": { "typia": "../" diff --git a/test-error/src/llm/llm.application.length.ts b/test-error/src/llm/llm.application.length.ts new file mode 100644 index 0000000000..07da188f52 --- /dev/null +++ b/test-error/src/llm/llm.application.length.ts @@ -0,0 +1,19 @@ +import typia from "typia"; + +typia.llm.application< + { + _aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(p: { + x: number; + y: number; + z: number; + }): void; + }, + "chatgpt" +>(); + +typia.llm.application<{ + /** + * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + */ + something(p: { value: string }): void; +}>(); diff --git a/test-esm/package.json b/test-esm/package.json index 2f13861b66..18168d2379 100644 --- a/test-esm/package.json +++ b/test-esm/package.json @@ -34,7 +34,7 @@ "@types/cli": "^0.11.25", "@types/node": "^22.10.1", "ts-patch": "^3.3.0", - "typescript": "~5.7.2" + "typescript": "~5.7.3" }, "dependencies": { "typia": "../" diff --git a/test/package.json b/test/package.json index cd7d180415..109e6587fb 100644 --- a/test/package.json +++ b/test/package.json @@ -44,7 +44,7 @@ "rimraf": "^5.0.5", "ts-node": "^10.9.2", "ts-patch": "^3.3.0", - "typescript": "~5.7.2" + "typescript": "~5.7.3" }, "dependencies": { "cli": "^1.0.1", diff --git a/test/src/debug/llm.ts b/test/src/debug/llm.ts new file mode 100644 index 0000000000..878ab3b510 --- /dev/null +++ b/test/src/debug/llm.ts @@ -0,0 +1,10 @@ +import typia from "typia"; + +interface Application { + /** + * Something interesting. + */ + something(): void; +} + +typia.llm.applicationOfValidate(); diff --git a/website/package.json b/website/package.json index 15b8928407..6fc858cd0a 100644 --- a/website/package.json +++ b/website/package.json @@ -38,7 +38,7 @@ "react-dom": "^18.2.0", "tgrid": "^1.0.3", "tstl": "^3.0.0", - "typescript": "~5.7.2", + "typescript": "~5.7.3", "typia": "latest" }, "devDependencies": { From e62aa2c76bef1a72d8ad90efd9dd4f358a8a2d9f Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Tue, 14 Jan 2025 01:50:50 +0900 Subject: [PATCH 2/2] Test PR 1459 --- ...t_pr_1459_llm_application_empty_parameters.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test/src/features/issues/test_pr_1459_llm_application_empty_parameters.ts diff --git a/test/src/features/issues/test_pr_1459_llm_application_empty_parameters.ts b/test/src/features/issues/test_pr_1459_llm_application_empty_parameters.ts new file mode 100644 index 0000000000..bb13d6c479 --- /dev/null +++ b/test/src/features/issues/test_pr_1459_llm_application_empty_parameters.ts @@ -0,0 +1,16 @@ +import typia from "typia"; + +import { TestValidator } from "../../helpers/TestValidator"; + +export const test_pr_1459_llm_application_empty_parameters = (): void => { + const app = typia.llm.applicationOfValidate(); + const result = app.functions[0]!.validate({}); + TestValidator.equals("success")(result.success)(true); +}; + +interface Application { + /** + * Something interesting. + */ + something(): void; +}