Skip to content

Commit

Permalink
Fix gemini schema's oneOf detection logic.
Browse files Browse the repository at this point in the history
  • Loading branch information
samchon committed Nov 29, 2024
1 parent 400aa29 commit 1700b31
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@samchon/openapi",
"version": "2.0.0-dev.20241129",
"version": "2.0.0-dev.20241129-4",
"description": "OpenAPI definitions and converters for 'typia' and 'nestia'.",
"main": "./lib/index.js",
"module": "./lib/index.mjs",
Expand Down
31 changes: 18 additions & 13 deletions src/converters/ChatGptConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export namespace ChatGptConverter {
reference: props.config.reference,
constraint: false,
},
validate: validate(props.errors),
});
if (params === null) return null;
for (const key of Object.keys(params.$defs))
Expand All @@ -44,19 +45,7 @@ export namespace ChatGptConverter {
reference: props.config.reference,
constraint: false,
},
validate: (schema, accessor) => {
if (
OpenApiTypeChecker.isObject(schema) &&
!!schema.additionalProperties
) {
if (props.errors)
props.errors.push(
`${accessor}.additionalProperties: ChatGPT does not allow additionalProperties, the dynamic key typed object.`,
);
return false;
}
return true;
},
validate: validate(props.errors),
});
if (schema === null) return null;
for (const key of Object.keys(props.$defs))
Expand All @@ -65,6 +54,22 @@ export namespace ChatGptConverter {
return transform(schema);
};

const validate =
(errors: string[] | undefined) =>
(schema: OpenApi.IJsonSchema, accessor: string): boolean => {
if (
OpenApiTypeChecker.isObject(schema) &&
!!schema.additionalProperties
) {
if (errors)
errors.push(
`${accessor}.additionalProperties: ChatGPT does not allow additionalProperties, the dynamic key typed object.`,
);
return false;
}
return true;
};

const transform = (schema: ILlmSchemaV3_1): IChatGptSchema => {
const union: Array<IChatGptSchema> = [];
const attribute: IChatGptSchema.__IAttribute = {
Expand Down
35 changes: 35 additions & 0 deletions src/converters/GeminiConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { OpenApi } from "../OpenApi";
import { IGeminiSchema } from "../structures/IGeminiSchema";
import { ILlmSchemaV3 } from "../structures/ILlmSchemaV3";
import { LlmTypeCheckerV3 } from "../utils/LlmTypeCheckerV3";
import { MapUtil } from "../utils/MapUtil";
import { OpenApiTypeChecker } from "../utils/OpenApiTypeChecker";
import { LlmConverterV3 } from "./LlmConverterV3";
import { LlmParametersFinder } from "./LlmParametersFinder";
Expand Down Expand Up @@ -47,6 +48,40 @@ export namespace GeminiConverter {
return false;
}
} else if (OpenApiTypeChecker.isOneOf(next)) {
// NULLABLE CASE
const notNull = next.oneOf.filter(
(v) => OpenApiTypeChecker.isNull(v) === false,
);
if (notNull.length < 2) return true;

// ENUM CASE
const constants: OpenApi.IJsonSchema.IConstant[] = notNull.filter(
(v) => OpenApiTypeChecker.isConstant(v),
);
const dict: Map<"boolean" | "number" | "string", any> = new Map();
for (const v of constants)
MapUtil.take(dict)(typeof v.const as "number")(() => []).push(
v.const,
);
if (dict.size === 1) {
if (notNull.length === constants.length) return true;
const atomic = notNull.filter(
(v) =>
OpenApiTypeChecker.isBoolean(v) ||
OpenApiTypeChecker.isInteger(v) ||
OpenApiTypeChecker.isNumber(v) ||
OpenApiTypeChecker.isString(v),
);
if (atomic.length === 1)
if (atomic[0].type === "integer")
return (
dict.has("number") &&
dict.get("number")!.every((v: number) => Number.isInteger(v))
);
else return dict.has(atomic[0].type);
}

// REAL ONE-OF TYPE
if (props.errors)
props.errors.push(`${accessor}: Gemini does not allow union type.`);
return false;
Expand Down
1 change: 1 addition & 0 deletions src/converters/LlmConverterV3_1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export namespace LlmConverterV3_1 {
schema: OpenApi.IJsonSchema.IObject | OpenApi.IJsonSchema.IReference;
errors?: string[];
accessor?: string;
validate?: (input: OpenApi.IJsonSchema, accessor: string) => boolean;
}): ILlmSchemaV3_1.IParameters | null => {
const entity: OpenApi.IJsonSchema.IObject | null =
LlmParametersFinder.find(props);
Expand Down
25 changes: 25 additions & 0 deletions test/features/llm/gemini/test_gemini_schema_enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TestValidator } from "@nestia/e2e";
import { LlmSchemaConverter } from "@samchon/openapi/lib/converters/LlmSchemaConverter";
import typia, { IJsonSchemaCollection, tags } from "typia";

export const test_gemini_schema_enum = (): void => {
const collection: IJsonSchemaCollection =
typia.json.schemas<
[
0 | 1 | 2,
(number & {}) | 1.2 | 2.3 | 3.4,
(number & tags.Type<"int32">) | 1 | 2 | 3,
"one" | "two" | "three",
]
>();
for (const schema of collection.schemas) {
const errors: string[] = [];
const gemini = LlmSchemaConverter.schema("gemini")({
config: LlmSchemaConverter.defaultConfig("gemini"),
components: collection.components,
schema,
errors,
});
TestValidator.equals("success")(!!gemini)(true);
}
};
46 changes: 46 additions & 0 deletions test/features/llm/gemini/test_gemini_schema_nullable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TestValidator } from "@nestia/e2e";
import { LlmSchemaConverter } from "@samchon/openapi/lib/converters/LlmSchemaConverter";
import typia, { IJsonSchemaCollection } from "typia";

export const test_gemini_schema_nullable = (): void => {
const collection: IJsonSchemaCollection = typia.json.schemas<
[
0 | 1 | 2 | 3 | null,
(number & {}) | 1.2 | 2.3 | 3.4 | null,
{
id: string | null;
value: number;
} | null,
Array<number> | null,
Array<{
nested: Array<{
id: string | null;
}>;
nullable: Array<string | null>;
}>,
{
first: ToJsonNull;
second: ToJsonNull | null;
},
{
first: ToJsonNull | null;
second: ToJsonNull | null;
third?: ToJsonNull | null;
},
]
>();
for (const schema of collection.schemas) {
const errors: string[] = [];
const gemini = LlmSchemaConverter.schema("gemini")({
config: LlmSchemaConverter.defaultConfig("gemini"),
components: collection.components,
schema,
errors,
});
TestValidator.equals("success")(!!gemini)(true);
}
};

interface ToJsonNull {
toJSON: () => null;
}

0 comments on commit 1700b31

Please sign in to comment.