Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance ChatGptTypeChecker and LlmTypeCheckerV3. #113

Merged
merged 1 commit into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.2.0",
"version": "2.2.1",
"description": "OpenAPI definitions and converters for 'typia' and 'nestia'.",
"main": "./lib/index.js",
"module": "./lib/index.mjs",
Expand Down
30 changes: 26 additions & 4 deletions src/utils/ChatGptTypeChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,15 @@ export namespace ChatGptTypeChecker {
if (found !== undefined) next(found, `${refAccessor}[${key}]`);
} else if (ChatGptTypeChecker.isAnyOf(schema))
schema.anyOf.forEach((s, i) => next(s, `${accessor}.anyOf[${i}]`));
else if (ChatGptTypeChecker.isObject(schema))
else if (ChatGptTypeChecker.isObject(schema)) {
for (const [key, value] of Object.entries(schema.properties))
next(value, `${accessor}.properties[${JSON.stringify(key)}]`);
else if (ChatGptTypeChecker.isArray(schema))
if (
typeof schema.additionalProperties === "object" &&
schema.additionalProperties !== null
)
next(schema.additionalProperties, `${accessor}.additionalProperties`);
} else if (ChatGptTypeChecker.isArray(schema))
next(schema.items, `${accessor}.items`);
};
next(props.schema, props.accessor ?? "$input.schemas");
Expand Down Expand Up @@ -288,8 +293,24 @@ export namespace ChatGptTypeChecker {
visited: Map<IChatGptSchema, Map<IChatGptSchema, boolean>>;
x: IChatGptSchema.IObject;
y: IChatGptSchema.IObject;
}): boolean =>
Object.entries(p.y.properties ?? {}).every(([key, b]) => {
}): boolean => {
if (!p.x.additionalProperties && !!p.y.additionalProperties) return false;
else if (
!!p.x.additionalProperties &&
!!p.y.additionalProperties &&
((typeof p.x.additionalProperties === "object" &&
p.y.additionalProperties === true) ||
(typeof p.x.additionalProperties === "object" &&
typeof p.y.additionalProperties === "object" &&
!coverStation({
$defs: p.$defs,
visited: p.visited,
x: p.x.additionalProperties,
y: p.y.additionalProperties,
})))
)
return false;
return Object.entries(p.y.properties ?? {}).every(([key, b]) => {
const a: IChatGptSchema | undefined = p.x.properties?.[key];
if (a === undefined) return false;
else if (
Expand All @@ -304,6 +325,7 @@ export namespace ChatGptTypeChecker {
y: b,
});
});
};

const coverBoolean = (
x: IChatGptSchema.IBoolean,
Expand Down
168 changes: 168 additions & 0 deletions src/utils/LlmTypeCheckerV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,174 @@ export namespace LlmTypeCheckerV3 {
});
};

export const covers = (x: ILlmSchemaV3, y: ILlmSchemaV3): boolean => {
const alpha: ILlmSchemaV3[] = flatSchema(x);
const beta: ILlmSchemaV3[] = flatSchema(y);
if (alpha.some((x) => isUnknown(x))) return true;
else if (beta.some((x) => isUnknown(x))) return false;
return beta.every((b) =>
alpha.some((a) => {
// CHECK EQUALITY
if (a === b) return true;
else if (isUnknown(a)) return true;
else if (isUnknown(b)) return false;
else if (isNullOnly(a)) return isNullOnly(b);
else if (isNullOnly(b)) return isNullable(a);
else if (isNullable(a) && !isNullable(b)) return false;
// ATOMIC CASE
else if (isBoolean(a)) return isBoolean(b) && coverBoolean(a, b);
else if (isInteger(a)) return isInteger(b) && coverInteger(a, b);
else if (isNumber(a))
return (isNumber(b) || isInteger(b)) && coverNumber(a, b);
else if (isString(a)) return isString(b) && covertString(a, b);
// INSTANCE CASE
else if (isArray(a)) return isArray(b) && coverArray(a, b);
else if (isObject(a)) return isObject(b) && coverObject(a, b);
else if (isOneOf(a)) return false;
}),
);
};

/**
* @internal
*/
const coverBoolean = (
x: ILlmSchemaV3.IBoolean,
y: ILlmSchemaV3.IBoolean,
): boolean =>
x.enum === undefined ||
(y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v)));

/**
* @internal
*/
const coverInteger = (
x: ILlmSchemaV3.IInteger,
y: ILlmSchemaV3.IInteger,
): boolean => {
if (x.enum !== undefined)
return y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v));
return [
x.type === y.type,
x.minimum === undefined ||
(y.minimum !== undefined && x.minimum <= y.minimum),
x.maximum === undefined ||
(y.maximum !== undefined && x.maximum >= y.maximum),
x.exclusiveMinimum !== true ||
x.minimum === undefined ||
(y.minimum !== undefined &&
(y.exclusiveMinimum === true || x.minimum < y.minimum)),
x.exclusiveMaximum !== true ||
x.maximum === undefined ||
(y.maximum !== undefined &&
(y.exclusiveMaximum === true || x.maximum > y.maximum)),
x.multipleOf === undefined ||
(y.multipleOf !== undefined &&
y.multipleOf / x.multipleOf ===
Math.floor(y.multipleOf / x.multipleOf)),
].every((v) => v);
};

/**
* @internal
*/
const coverNumber = (
x: ILlmSchemaV3.INumber,
y: ILlmSchemaV3.INumber | ILlmSchemaV3.IInteger,
): boolean => {
if (x.enum !== undefined)
return y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v));
return [
x.type === y.type || (x.type === "number" && y.type === "integer"),
x.minimum === undefined ||
(y.minimum !== undefined && x.minimum <= y.minimum),
x.maximum === undefined ||
(y.maximum !== undefined && x.maximum >= y.maximum),
x.exclusiveMinimum !== true ||
x.minimum === undefined ||
(y.minimum !== undefined &&
(y.exclusiveMinimum === true || x.minimum < y.minimum)),
x.exclusiveMaximum !== true ||
x.maximum === undefined ||
(y.maximum !== undefined &&
(y.exclusiveMaximum === true || x.maximum > y.maximum)),
x.multipleOf === undefined ||
(y.multipleOf !== undefined &&
y.multipleOf / x.multipleOf ===
Math.floor(y.multipleOf / x.multipleOf)),
].every((v) => v);
};

/**
* @internal
*/
const covertString = (
x: ILlmSchemaV3.IString,
y: ILlmSchemaV3.IString,
): boolean => {
if (x.enum !== undefined)
return y.enum !== undefined && x.enum.every((v) => y.enum!.includes(v));
return [
x.type === y.type,
x.format === undefined ||
(y.format !== undefined && coverFormat(x.format, y.format)),
x.pattern === undefined || x.pattern === y.pattern,
x.minLength === undefined ||
(y.minLength !== undefined && x.minLength <= y.minLength),
x.maxLength === undefined ||
(y.maxLength !== undefined && x.maxLength >= y.maxLength),
].every((v) => v);
};

const coverFormat = (
x: Required<ILlmSchemaV3.IString>["format"],
y: Required<ILlmSchemaV3.IString>["format"],
): boolean =>
x === y ||
(x === "idn-email" && y === "email") ||
(x === "idn-hostname" && y === "hostname") ||
(["uri", "iri"].includes(x) && y === "url") ||
(x === "iri" && y === "uri") ||
(x === "iri-reference" && y === "uri-reference");

/**
* @internal
*/
const coverArray = (
x: ILlmSchemaV3.IArray,
y: ILlmSchemaV3.IArray,
): boolean => covers(x.items, y.items);

const coverObject = (
x: ILlmSchemaV3.IObject,
y: ILlmSchemaV3.IObject,
): boolean => {
if (!x.additionalProperties && !!y.additionalProperties) return false;
else if (
(!!x.additionalProperties &&
!!y.additionalProperties &&
typeof x.additionalProperties === "object" &&
y.additionalProperties === true) ||
(typeof x.additionalProperties === "object" &&
typeof y.additionalProperties === "object" &&
!covers(x.additionalProperties, y.additionalProperties))
)
return false;
return Object.entries(y.properties ?? {}).every(([key, b]) => {
const a: ILlmSchemaV3 | undefined = x.properties?.[key];
if (a === undefined) return false;
else if (
(x.required?.includes(key) ?? false) === true &&
(y.required?.includes(key) ?? false) === false
)
return false;
return covers(a, b);
});
};

const flatSchema = (schema: ILlmSchemaV3): ILlmSchemaV3[] =>
isOneOf(schema) ? schema.oneOf.flatMap(flatSchema) : [schema];

/* -----------------------------------------------------------
TYPE CHECKERS
----------------------------------------------------------- */
Expand Down
63 changes: 63 additions & 0 deletions test/features/llm/validate_llm_type_checker_cover_any.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { TestValidator } from "@nestia/e2e";
import { ILlmSchema, OpenApi } from "@samchon/openapi";
import { LlmSchemaComposer } from "@samchon/openapi/lib/composers/LlmSchemaComposer";
import typia, { IJsonSchemaCollection } from "typia";

export const test_chatgpt_type_checker_cover_any = (): void =>
validate_llm_type_checker_cover_any("chatgpt");

export const test_claude_type_checker_cover_any = (): void =>
validate_llm_type_checker_cover_any("claude");

export const test_gemini_type_checker_cover_any = (): void =>
validate_llm_type_checker_cover_any("gemini");

export const test_llama_type_checker_cover_any = (): void =>
validate_llm_type_checker_cover_any("llama");

export const test_llm_v30_type_checker_cover_any = (): void =>
validate_llm_type_checker_cover_any("3.0");

export const test_llm_v31_type_checker_cover_any = (): void =>
validate_llm_type_checker_cover_any("3.1");

const validate_llm_type_checker_cover_any = <Model extends ILlmSchema.Model>(
model: Model,
) => {
const collection: IJsonSchemaCollection = typia.json.schemas<[IBasic]>();
const result = LlmSchemaComposer.parameters(model)({
config: LlmSchemaComposer.defaultConfig(model) as any,
components: collection.components,
schema: collection.schemas[0] as OpenApi.IJsonSchema.IReference,
});
if (result.success === false)
throw new Error(`Failed to compose ${model} parameters.`);

const parameters = result.value;
const check = (x: ILlmSchema<Model>, y: ILlmSchema<Model>): boolean =>
model === "3.0" || model === "gemini"
? (LlmSchemaComposer.typeChecker(model).covers as any)(x, y)
: (LlmSchemaComposer.typeChecker(model).covers as any)({
x,
y,
$defs: (parameters as any).$defs,
});
TestValidator.equals("any covers (string | null)")(true)(
check(
parameters.properties.any as ILlmSchema<Model>,
parameters.properties.string_or_null as ILlmSchema<Model>,
),
);
TestValidator.equals("any covers (string | undefined)")(true)(
check(
parameters.properties.any as ILlmSchema<Model>,
parameters.properties.string_or_undefined as ILlmSchema<Model>,
),
);
};

interface IBasic {
any: any;
string_or_null: null | string;
string_or_undefined: string | undefined;
}
Loading
Loading