Skip to content

Commit

Permalink
Merge pull request #113 from samchon/feat/chatgpt
Browse files Browse the repository at this point in the history
Enhance `ChatGptTypeChecker` and `LlmTypeCheckerV3`.
  • Loading branch information
samchon authored Dec 16, 2024
2 parents 6f0f412 + 4c37a42 commit 955f41a
Show file tree
Hide file tree
Showing 5 changed files with 469 additions and 5 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.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

0 comments on commit 955f41a

Please sign in to comment.