diff --git a/package.json b/package.json index 54a3976..5e3653f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "2.0.0-dev.20241111-2", + "version": "2.0.0-dev.20241111-3", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "module": "./lib/index.mjs", diff --git a/src/converters/HttpLlmConverter.ts b/src/converters/HttpLlmConverter.ts index a249e88..7c99789 100644 --- a/src/converters/HttpLlmConverter.ts +++ b/src/converters/HttpLlmConverter.ts @@ -42,7 +42,32 @@ export namespace HttpLlmConverter { const functions: IHttpLlmFunction<Schema, Operation, Route>[] = props.migrate.routes .map((route) => { - if (route.method === "head") return null; + if (route.method === "head") { + errors.push({ + method: route.method, + path: route.path, + messages: [ + "HEAD method is not supported in the LLM application.", + ], + operation: () => route.operation(), + route: () => route as any as Route, + }); + return null; + } else if ( + route.body?.type === "multipart/form-data" || + route.success?.type === "multipart/form-data" + ) { + errors.push({ + method: route.method, + path: route.path, + messages: [ + `The "multipart/form-data" content type is not supported in the LLM application.`, + ], + operation: () => route.operation(), + route: () => route as any as Route, + }); + return null; + } const func: IHttpLlmFunction<Schema> | null = composeFunction({ model: props.model, options: props.options, diff --git a/test/controllers/AppController.ts b/test/controllers/AppController.ts index 2dd2833..1b15fbc 100644 --- a/test/controllers/AppController.ts +++ b/test/controllers/AppController.ts @@ -69,9 +69,6 @@ export class AppController { }; } - /** - * @deprecated - */ @TypedRoute.Post(":index/:level/:optimal/multipart") public query_multipart( @TypedParam("index") @@ -94,6 +91,9 @@ export class AppController { }; } + /** + * @deprecated + */ @TypedRoute.Get("nothing") public nothing(): void {} } diff --git a/test/features/llm/chatgpt/test_chatgpt_schema_ref.ts b/test/features/llm/chatgpt/test_chatgpt_schema_ref.ts new file mode 100644 index 0000000..6e103da --- /dev/null +++ b/test/features/llm/chatgpt/test_chatgpt_schema_ref.ts @@ -0,0 +1,85 @@ +import { TestValidator } from "@nestia/e2e"; +import { IChatGptSchema } from "@samchon/openapi"; +import { ChatGptConverter } from "@samchon/openapi/lib/converters/ChatGptConverter"; +import typia, { IJsonApplication, tags } from "typia"; + +export const test_chatgpt_schema_ref = (): void => { + test(typia.json.application<[IShoppingCategory]>(), { + $ref: "#/$defs/IShoppingCategory", + $defs: { + IShoppingCategory: { + type: "object", + properties: { + id: { + type: "string", + format: "uuid", + }, + name: { + type: "string", + }, + children: { + type: "array", + items: { + $ref: "#/$defs/IShoppingCategory", + }, + }, + }, + additionalProperties: false, + required: ["id", "name", "children"], + }, + }, + }); + test(typia.json.application<[IShoppingCategory.IInvert]>(), { + $ref: "#/$defs/IShoppingCategory.IInvert", + $defs: { + "IShoppingCategory.IInvert": { + type: "object", + properties: { + id: { + type: "string", + format: "uuid", + }, + name: { + type: "string", + }, + parent: { + oneOf: [ + { + type: "null", + }, + { + $ref: "#/$defs/IShoppingCategory.IInvert", + }, + ], + }, + }, + required: ["id", "name", "parent"], + additionalProperties: false, + }, + }, + }); +}; + +const test = ( + collection: IJsonApplication, + expected: IChatGptSchema.ITop, +): void => { + const schema: IChatGptSchema.ITop | null = ChatGptConverter.schema({ + components: collection.components, + schema: collection.schemas[0], + }); + TestValidator.equals("ref")(schema)(expected); +}; + +interface IShoppingCategory { + id: string & tags.Format<"uuid">; + name: string; + children: IShoppingCategory[]; +} +namespace IShoppingCategory { + export interface IInvert { + id: string & tags.Format<"uuid">; + name: string; + parent: IShoppingCategory.IInvert | null; + } +} diff --git a/test/features/llm/test_http_llm_function_deprecated.ts b/test/features/llm/test_http_llm_function_deprecated.ts index d71af62..87d1ab8 100644 --- a/test/features/llm/test_http_llm_function_deprecated.ts +++ b/test/features/llm/test_http_llm_function_deprecated.ts @@ -20,9 +20,7 @@ export const test_http_llm_function_deprecated = (): void => { }); const func: IHttpLlmFunction<ILlmSchemaV3> | undefined = application.functions.find( - (f) => - f.method === "post" && - f.path === "/{index}/{level}/{optimal}/multipart", + (f) => f.method === "get" && f.path === "/nothing", ); TestValidator.equals("deprecated")(func?.deprecated)(true); }; diff --git a/test/features/llm/test_http_llm_function_multipart.ts b/test/features/llm/test_http_llm_function_multipart.ts new file mode 100644 index 0000000..77336cf --- /dev/null +++ b/test/features/llm/test_http_llm_function_multipart.ts @@ -0,0 +1,22 @@ +import { TestValidator } from "@nestia/e2e"; +import { HttpLlm, IHttpLlmApplication, OpenApi } from "@samchon/openapi"; + +import swagger from "../../swagger.json"; + +export const test_http_llm_function_multipart = (): void => { + const document: OpenApi.IDocument = OpenApi.convert(swagger as any); + const application: IHttpLlmApplication<"3.0"> = HttpLlm.application({ + model: "3.0", + document, + options: { + keyword: true, + }, + }); + TestValidator.equals("multipart not suppported")( + !!application.errors.find( + (e) => + e.method === "post" && + e.path === "/{index}/{level}/{optimal}/multipart", + ), + )(true); +}; diff --git a/test/features/llm/test_llm_schema_recursive_array.ts b/test/features/llm/test_llm_schema_recursive_array.ts index 0d2e3d7..4a9b6e2 100644 --- a/test/features/llm/test_llm_schema_recursive_array.ts +++ b/test/features/llm/test_llm_schema_recursive_array.ts @@ -8,6 +8,10 @@ export const test_llm_schema_recursive_array = (): void => { Department: { type: "object", properties: { + id: { + type: "string", + format: "uuid", + }, name: { type: "string", }, @@ -18,7 +22,7 @@ export const test_llm_schema_recursive_array = (): void => { }, }, }, - required: ["name", "children"], + required: ["id", "name", "children"], }, }, }, @@ -30,6 +34,10 @@ export const test_llm_schema_recursive_array = (): void => { TestValidator.equals("recursive")(schema)({ type: "object", properties: { + id: { + type: "string", + format: "uuid", + }, name: { type: "string", }, @@ -38,6 +46,10 @@ export const test_llm_schema_recursive_array = (): void => { items: { type: "object", properties: { + id: { + type: "string", + format: "uuid", + }, name: { type: "string", }, @@ -46,6 +58,10 @@ export const test_llm_schema_recursive_array = (): void => { items: { type: "object", properties: { + id: { + type: "string", + format: "uuid", + }, name: { type: "string", }, @@ -54,6 +70,10 @@ export const test_llm_schema_recursive_array = (): void => { items: { type: "object", properties: { + id: { + type: "string", + format: "uuid", + }, name: { type: "string", }, @@ -63,22 +83,22 @@ export const test_llm_schema_recursive_array = (): void => { maxItems: 0, }, }, - required: ["name", "children"], + required: ["id", "name", "children"], additionalProperties: false, }, }, }, - required: ["name", "children"], + required: ["id", "name", "children"], additionalProperties: false, }, }, }, - required: ["name", "children"], + required: ["id", "name", "children"], additionalProperties: false, }, }, }, - required: ["name", "children"], + required: ["id", "name", "children"], additionalProperties: false, }); }; diff --git a/test/swagger.json b/test/swagger.json index 6e83b2b..e6f3886 100644 --- a/test/swagger.json +++ b/test/swagger.json @@ -7,7 +7,7 @@ } ], "info": { - "version": "1.1.1-dev.20241008-2", + "version": "2.0.0-dev.20241111-3", "title": "@samchon/openapi", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "license": { @@ -275,7 +275,6 @@ }, "/{index}/{level}/{optimal}/multipart": { "post": { - "deprecated": true, "tags": [], "parameters": [ { @@ -377,6 +376,7 @@ }, "/nothing": { "get": { + "deprecated": true, "tags": [], "parameters": [], "responses": {