diff --git a/package.json b/package.json index 145bf15..a56fad3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "2.0.3", + "version": "2.0.4", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "module": "./lib/index.mjs", diff --git a/src/composers/llm/ChatGptSchemaComposer.ts b/src/composers/llm/ChatGptSchemaComposer.ts index 4ae2299..54a9c62 100644 --- a/src/composers/llm/ChatGptSchemaComposer.ts +++ b/src/composers/llm/ChatGptSchemaComposer.ts @@ -260,6 +260,13 @@ export namespace ChatGptSchemaComposer { predicate: (schema: IChatGptSchema) => boolean; schema: IChatGptSchema.IObject; }): [IChatGptSchema.IObject | null, IChatGptSchema.IObject | null] => { + // EMPTY OBJECT + if ( + Object.keys(props.schema.properties ?? {}).length === 0 && + !!props.schema.additionalProperties === false + ) + return [props.schema, null]; + const llm = { ...props.schema, properties: {} as Record, @@ -268,6 +275,7 @@ export namespace ChatGptSchemaComposer { ...props.schema, properties: {} as Record, } satisfies IChatGptSchema.IObject; + for (const [key, value] of Object.entries(props.schema.properties ?? {})) { const [x, y] = separateStation({ $defs: props.$defs, diff --git a/src/composers/llm/LlmSchemaV3Composer.ts b/src/composers/llm/LlmSchemaV3Composer.ts index abdba3a..8b98900 100644 --- a/src/composers/llm/LlmSchemaV3Composer.ts +++ b/src/composers/llm/LlmSchemaV3Composer.ts @@ -219,6 +219,13 @@ export namespace LlmSchemaV3Composer { predicate: (schema: ILlmSchemaV3) => boolean; schema: ILlmSchemaV3.IObject; }): [ILlmSchemaV3.IObject | null, ILlmSchemaV3.IObject | null] => { + // EMPTY OBJECT + if ( + Object.keys(props.schema.properties ?? {}).length === 0 && + !!props.schema.additionalProperties === false + ) + return [props.schema, null]; + const llm = { ...props.schema, properties: {} as Record, @@ -229,6 +236,7 @@ export namespace LlmSchemaV3Composer { properties: {} as Record, additionalProperties: props.schema.additionalProperties, } satisfies ILlmSchemaV3.IObject; + for (const [key, value] of Object.entries(props.schema.properties ?? {})) { const [x, y] = separateStation({ predicate: props.predicate, diff --git a/src/composers/llm/LlmSchemaV3_1Composer.ts b/src/composers/llm/LlmSchemaV3_1Composer.ts index 9f62105..334394c 100644 --- a/src/composers/llm/LlmSchemaV3_1Composer.ts +++ b/src/composers/llm/LlmSchemaV3_1Composer.ts @@ -424,6 +424,13 @@ export namespace LlmSchemaV3_1Composer { predicate: (schema: ILlmSchemaV3_1) => boolean; schema: ILlmSchemaV3_1.IObject; }): [ILlmSchemaV3_1.IObject | null, ILlmSchemaV3_1.IObject | null] => { + // EMPTY OBJECT + if ( + Object.keys(props.schema.properties ?? {}).length === 0 && + !!props.schema.additionalProperties === false + ) + return [props.schema, null]; + const llm = { ...props.schema, properties: {} as Record, @@ -433,6 +440,7 @@ export namespace LlmSchemaV3_1Composer { ...props.schema, properties: {} as Record, } satisfies ILlmSchemaV3_1.IObject; + for (const [key, value] of Object.entries(props.schema.properties ?? {})) { const [x, y] = separateStation({ $defs: props.$defs, diff --git a/test/features/llm/validate_llm_schema_separate_object_empty.ts b/test/features/llm/validate_llm_schema_separate_object_empty.ts new file mode 100644 index 0000000..131e6ef --- /dev/null +++ b/test/features/llm/validate_llm_schema_separate_object_empty.ts @@ -0,0 +1,64 @@ +import { TestValidator } from "@nestia/e2e"; +import { + ILlmSchema, + IOpenApiSchemaError, + IResult, + OpenApi, + OpenApiTypeChecker, +} from "@samchon/openapi"; +import { LlmSchemaComposer } from "@samchon/openapi/lib/composers/LlmSchemaComposer"; +import typia, { IJsonSchemaCollection } from "typia"; + +export const test_chatgpt_schema_separate_object_empty = (): void => + validate_llm_schema_separate_object_empty("chatgpt"); + +export const test_claude_schema_separate_object_empty = (): void => + validate_llm_schema_separate_object_empty("claude"); + +export const test_gemini_schema_separate_object_empty = (): void => + validate_llm_schema_separate_object_empty("gemini"); + +export const test_llama_schema_separate_object_empty = (): void => + validate_llm_schema_separate_object_empty("llama"); + +export const test_llm_v30_schema_separate_object_empty = (): void => + validate_llm_schema_separate_object_empty("3.0"); + +export const test_llm_v31_schema_separate_object_empty = (): void => + validate_llm_schema_separate_object_empty("3.1"); + +const validate_llm_schema_separate_object_empty = < + Model extends ILlmSchema.Model, +>( + model: Model, +): void => { + TestValidator.equals("separated")( + LlmSchemaComposer.separateParameters(model)({ + predicate: ((schema: OpenApi.IJsonSchema) => + OpenApiTypeChecker.isInteger(schema)) as any, + parameters: schema(model)(typia.json.schemas<[{}]>()) as any, + }), + )({ + llm: schema(model)(typia.json.schemas<[{}]>()) as any, + human: null, + }); +}; + +const schema = + (model: Model) => + (collection: IJsonSchemaCollection): ILlmSchema.IParameters => { + const result: IResult< + ILlmSchema.IParameters, + IOpenApiSchemaError + > = LlmSchemaComposer.parameters(model)({ + components: collection.components, + schema: typia.assert< + OpenApi.IJsonSchema.IObject | OpenApi.IJsonSchema.IReference + >(collection.schemas[0]), + config: LlmSchemaComposer.defaultConfig( + model, + ) satisfies ILlmSchema.IConfig as any, + }) as IResult, IOpenApiSchemaError>; + if (result.success === false) throw new Error("Invalid schema"); + return result.value; + }; diff --git a/test/utils/ChatGptFunctionCaller.ts b/test/utils/ChatGptFunctionCaller.ts index c864509..5adee12 100644 --- a/test/utils/ChatGptFunctionCaller.ts +++ b/test/utils/ChatGptFunctionCaller.ts @@ -58,10 +58,11 @@ export namespace ChatGptFunctionCaller { name: props.name, description: props.description, parameters: parameters.value as Record, - strict: true, }, }, ], + tool_choice: "required", + parallel_tool_calls: false, }); const toolCalls: OpenAI.ChatCompletionMessageToolCall[] = diff --git a/test/utils/ClaudeFunctionCaller.ts b/test/utils/ClaudeFunctionCaller.ts index 3f934a5..940371e 100644 --- a/test/utils/ClaudeFunctionCaller.ts +++ b/test/utils/ClaudeFunctionCaller.ts @@ -63,6 +63,10 @@ export namespace ClaudeFunctionCaller { input_schema: parameters.value as any, }, ], + tool_choice: { + type: "any", + disable_parallel_tool_use: true, + }, }); const toolCalls: Anthropic.ToolUseBlock[] = completion.content.filter(