@typia/utils 12.0.0-dev.20260306 → 12.0.0-dev.20260307
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/lib/converters/LlmSchemaConverter.d.ts +1 -20
- package/lib/converters/LlmSchemaConverter.js +0 -189
- package/lib/converters/LlmSchemaConverter.js.map +1 -1
- package/lib/converters/LlmSchemaConverter.mjs +0 -227
- package/lib/converters/LlmSchemaConverter.mjs.map +1 -1
- package/lib/http/HttpLlm.d.ts +2 -32
- package/lib/http/HttpLlm.js +4 -27
- package/lib/http/HttpLlm.js.map +1 -1
- package/lib/http/HttpLlm.mjs +1 -24
- package/lib/http/HttpLlm.mjs.map +1 -1
- package/lib/http/internal/HttpLlmApplicationComposer.js +13 -19
- package/lib/http/internal/HttpLlmApplicationComposer.js.map +1 -1
- package/lib/http/internal/HttpLlmApplicationComposer.mjs +7 -9
- package/lib/http/internal/HttpLlmApplicationComposer.mjs.map +1 -1
- package/lib/index.mjs +10 -10
- package/lib/utils/LlmJson.d.ts +67 -0
- package/lib/utils/LlmJson.js +106 -0
- package/lib/utils/LlmJson.js.map +1 -0
- package/lib/utils/LlmJson.mjs +113 -0
- package/lib/utils/LlmJson.mjs.map +1 -0
- package/lib/utils/index.d.ts +1 -1
- package/lib/utils/index.js +1 -1
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/index.mjs +1 -1
- package/lib/utils/internal/coerceLlmArguments.d.ts +1 -0
- package/lib/utils/internal/coerceLlmArguments.js +233 -0
- package/lib/utils/internal/coerceLlmArguments.js.map +1 -0
- package/lib/utils/internal/coerceLlmArguments.mjs +232 -0
- package/lib/utils/internal/coerceLlmArguments.mjs.map +1 -0
- package/lib/utils/internal/parseLenientJson.d.ts +1 -0
- package/lib/utils/internal/parseLenientJson.js +639 -0
- package/lib/utils/internal/parseLenientJson.js.map +1 -0
- package/lib/utils/internal/parseLenientJson.mjs +640 -0
- package/lib/utils/internal/parseLenientJson.mjs.map +1 -0
- package/lib/utils/internal/stringifyValidationFailure.d.ts +2 -0
- package/lib/utils/{stringifyValidationFailure.js → internal/stringifyValidationFailure.js} +55 -63
- package/lib/utils/internal/stringifyValidationFailure.js.map +1 -0
- package/lib/utils/{stringifyValidationFailure.mjs → internal/stringifyValidationFailure.mjs} +54 -63
- package/lib/utils/internal/stringifyValidationFailure.mjs.map +1 -0
- package/lib/validators/internal/OpenApiOneOfValidator.mjs +5 -1
- package/lib/validators/internal/OpenApiOneOfValidator.mjs.map +1 -1
- package/package.json +2 -2
- package/src/converters/LlmSchemaConverter.ts +0 -277
- package/src/http/HttpLlm.ts +1 -44
- package/src/http/internal/HttpLlmApplicationComposer.ts +3 -9
- package/src/utils/LlmJson.ts +115 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/internal/coerceLlmArguments.ts +297 -0
- package/src/utils/internal/parseLenientJson.ts +731 -0
- package/src/utils/{stringifyValidationFailure.ts → internal/stringifyValidationFailure.ts} +66 -70
- package/lib/http/internal/LlmDataMerger.d.ts +0 -48
- package/lib/http/internal/LlmDataMerger.js +0 -60
- package/lib/http/internal/LlmDataMerger.js.map +0 -1
- package/lib/http/internal/LlmDataMerger.mjs +0 -59
- package/lib/http/internal/LlmDataMerger.mjs.map +0 -1
- package/lib/utils/stringifyValidationFailure.d.ts +0 -25
- package/lib/utils/stringifyValidationFailure.js.map +0 -1
- package/lib/utils/stringifyValidationFailure.mjs.map +0 -1
- package/src/http/internal/LlmDataMerger.ts +0 -73
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IJsonSchemaAttribute,
|
|
3
3
|
IJsonSchemaTransformError,
|
|
4
|
-
ILlmFunction,
|
|
5
4
|
ILlmSchema,
|
|
6
5
|
IResult,
|
|
7
6
|
OpenApi,
|
|
8
7
|
} from "@typia/interface";
|
|
9
8
|
|
|
10
|
-
import { NamingConvention } from "../utils/NamingConvention";
|
|
11
9
|
import { JsonDescriptor } from "../utils/internal/JsonDescriptor";
|
|
12
10
|
import { LlmTypeChecker } from "../validators/LlmTypeChecker";
|
|
13
11
|
import { OpenApiTypeChecker } from "../validators/OpenApiTypeChecker";
|
|
14
|
-
import { OpenApiValidator } from "../validators/OpenApiValidator";
|
|
15
12
|
import { LlmDescriptionInverter } from "./internal/LlmDescriptionInverter";
|
|
16
13
|
import { LlmParametersFinder } from "./internal/LlmParametersComposer";
|
|
17
14
|
import { OpenApiConstraintShifter } from "./internal/OpenApiConstraintShifter";
|
|
@@ -28,7 +25,6 @@ import { OpenApiConstraintShifter } from "./internal/OpenApiConstraintShifter";
|
|
|
28
25
|
*
|
|
29
26
|
* - {@link parameters}: Convert object schema to {@link ILlmSchema.IParameters}
|
|
30
27
|
* - {@link schema}: Convert any schema to {@link ILlmSchema}
|
|
31
|
-
* - {@link separate}: Split parameters into LLM-fillable vs human-required
|
|
32
28
|
* - {@link invert}: Extract constraints from description back to schema
|
|
33
29
|
*
|
|
34
30
|
* Configuration options ({@link ILlmSchema.IConfig}):
|
|
@@ -624,279 +620,6 @@ export namespace LlmSchemaConverter {
|
|
|
624
620
|
}),
|
|
625
621
|
} satisfies OpenApi.IJsonSchema;
|
|
626
622
|
};
|
|
627
|
-
|
|
628
|
-
/* -----------------------------------------------------------
|
|
629
|
-
SEPARATORS
|
|
630
|
-
----------------------------------------------------------- */
|
|
631
|
-
/**
|
|
632
|
-
* Separate parameters into LLM and human parts.
|
|
633
|
-
*
|
|
634
|
-
* Splits parameters based on predicate (human-side if true). Creates separate
|
|
635
|
-
* schemas for LLM-fillable and human-required fields.
|
|
636
|
-
*
|
|
637
|
-
* @param props.parameters Parameters schema to separate
|
|
638
|
-
* @param props.predicate Returns true for human-side properties
|
|
639
|
-
* @param props.convention Key naming convention for separated types
|
|
640
|
-
* @param props.equals Whether to use strict equality validation
|
|
641
|
-
* @returns Separated LLM and human parameter schemas
|
|
642
|
-
*/
|
|
643
|
-
export const separate = (props: {
|
|
644
|
-
parameters: ILlmSchema.IParameters;
|
|
645
|
-
predicate: (schema: ILlmSchema) => boolean;
|
|
646
|
-
convention?: (key: string, type: "llm" | "human") => string;
|
|
647
|
-
equals?: boolean;
|
|
648
|
-
}): ILlmFunction.ISeparated => {
|
|
649
|
-
const convention =
|
|
650
|
-
props.convention ??
|
|
651
|
-
((key, type) => `${key}.${NamingConvention.capitalize(type)}`);
|
|
652
|
-
const [llm, human] = separateObject({
|
|
653
|
-
predicate: props.predicate,
|
|
654
|
-
convention,
|
|
655
|
-
$defs: props.parameters.$defs,
|
|
656
|
-
schema: props.parameters,
|
|
657
|
-
});
|
|
658
|
-
if (llm === null || human === null)
|
|
659
|
-
return {
|
|
660
|
-
llm: (llm as ILlmSchema.IParameters | null) ?? {
|
|
661
|
-
type: "object",
|
|
662
|
-
properties: {} as Record<string, ILlmSchema>,
|
|
663
|
-
required: [],
|
|
664
|
-
additionalProperties: false,
|
|
665
|
-
$defs: {},
|
|
666
|
-
},
|
|
667
|
-
human: human as ILlmSchema.IParameters | null,
|
|
668
|
-
};
|
|
669
|
-
const output: ILlmFunction.ISeparated = {
|
|
670
|
-
llm: {
|
|
671
|
-
...llm,
|
|
672
|
-
$defs: Object.fromEntries(
|
|
673
|
-
Object.entries(props.parameters.$defs).filter(([key]) =>
|
|
674
|
-
key.endsWith(".Llm"),
|
|
675
|
-
),
|
|
676
|
-
),
|
|
677
|
-
additionalProperties: false,
|
|
678
|
-
},
|
|
679
|
-
human: {
|
|
680
|
-
...human,
|
|
681
|
-
$defs: Object.fromEntries(
|
|
682
|
-
Object.entries(props.parameters.$defs).filter(([key]) =>
|
|
683
|
-
key.endsWith(".Human"),
|
|
684
|
-
),
|
|
685
|
-
),
|
|
686
|
-
additionalProperties: false,
|
|
687
|
-
},
|
|
688
|
-
};
|
|
689
|
-
for (const key of Object.keys(props.parameters.$defs))
|
|
690
|
-
if (key.endsWith(".Llm") === false && key.endsWith(".Human") === false)
|
|
691
|
-
delete props.parameters.$defs[key];
|
|
692
|
-
if (Object.keys(output.llm.properties).length !== 0) {
|
|
693
|
-
const components: OpenApi.IComponents = {};
|
|
694
|
-
output.validate = OpenApiValidator.create({
|
|
695
|
-
components,
|
|
696
|
-
schema: invert({
|
|
697
|
-
components,
|
|
698
|
-
schema: output.llm,
|
|
699
|
-
$defs: output.llm.$defs,
|
|
700
|
-
}),
|
|
701
|
-
required: true,
|
|
702
|
-
equals: props.equals,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
return output;
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
const separateStation = (props: {
|
|
709
|
-
predicate: (schema: ILlmSchema) => boolean;
|
|
710
|
-
convention: (key: string, type: "llm" | "human") => string;
|
|
711
|
-
$defs: Record<string, ILlmSchema>;
|
|
712
|
-
schema: ILlmSchema;
|
|
713
|
-
}): [ILlmSchema | null, ILlmSchema | null] => {
|
|
714
|
-
if (props.predicate(props.schema) === true) return [null, props.schema];
|
|
715
|
-
else if (
|
|
716
|
-
LlmTypeChecker.isUnknown(props.schema) ||
|
|
717
|
-
LlmTypeChecker.isAnyOf(props.schema)
|
|
718
|
-
)
|
|
719
|
-
return [props.schema, null];
|
|
720
|
-
else if (LlmTypeChecker.isObject(props.schema))
|
|
721
|
-
return separateObject({
|
|
722
|
-
predicate: props.predicate,
|
|
723
|
-
convention: props.convention,
|
|
724
|
-
$defs: props.$defs,
|
|
725
|
-
schema: props.schema,
|
|
726
|
-
});
|
|
727
|
-
else if (LlmTypeChecker.isArray(props.schema))
|
|
728
|
-
return separateArray({
|
|
729
|
-
predicate: props.predicate,
|
|
730
|
-
convention: props.convention,
|
|
731
|
-
$defs: props.$defs,
|
|
732
|
-
schema: props.schema,
|
|
733
|
-
});
|
|
734
|
-
else if (LlmTypeChecker.isReference(props.schema))
|
|
735
|
-
return separateReference({
|
|
736
|
-
predicate: props.predicate,
|
|
737
|
-
convention: props.convention,
|
|
738
|
-
$defs: props.$defs,
|
|
739
|
-
schema: props.schema,
|
|
740
|
-
});
|
|
741
|
-
return [props.schema, null];
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
const separateArray = (props: {
|
|
745
|
-
predicate: (schema: ILlmSchema) => boolean;
|
|
746
|
-
convention: (key: string, type: "llm" | "human") => string;
|
|
747
|
-
$defs: Record<string, ILlmSchema>;
|
|
748
|
-
schema: ILlmSchema.IArray;
|
|
749
|
-
}): [ILlmSchema.IArray | null, ILlmSchema.IArray | null] => {
|
|
750
|
-
const [x, y] = separateStation({
|
|
751
|
-
predicate: props.predicate,
|
|
752
|
-
convention: props.convention,
|
|
753
|
-
$defs: props.$defs,
|
|
754
|
-
schema: props.schema.items,
|
|
755
|
-
});
|
|
756
|
-
return [
|
|
757
|
-
x !== null
|
|
758
|
-
? {
|
|
759
|
-
...props.schema,
|
|
760
|
-
items: x,
|
|
761
|
-
}
|
|
762
|
-
: null,
|
|
763
|
-
y !== null
|
|
764
|
-
? {
|
|
765
|
-
...props.schema,
|
|
766
|
-
items: y,
|
|
767
|
-
}
|
|
768
|
-
: null,
|
|
769
|
-
];
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
const separateObject = (props: {
|
|
773
|
-
$defs: Record<string, ILlmSchema>;
|
|
774
|
-
predicate: (schema: ILlmSchema) => boolean;
|
|
775
|
-
convention: (key: string, type: "llm" | "human") => string;
|
|
776
|
-
schema: ILlmSchema.IObject;
|
|
777
|
-
}): [ILlmSchema.IObject | null, ILlmSchema.IObject | null] => {
|
|
778
|
-
// EMPTY OBJECT
|
|
779
|
-
if (
|
|
780
|
-
Object.keys(props.schema.properties ?? {}).length === 0 &&
|
|
781
|
-
!!props.schema.additionalProperties === false
|
|
782
|
-
)
|
|
783
|
-
return [props.schema, null];
|
|
784
|
-
|
|
785
|
-
const llm = {
|
|
786
|
-
...props.schema,
|
|
787
|
-
properties: {} as Record<string, ILlmSchema>,
|
|
788
|
-
additionalProperties: props.schema.additionalProperties,
|
|
789
|
-
} satisfies ILlmSchema.IObject;
|
|
790
|
-
const human = {
|
|
791
|
-
...props.schema,
|
|
792
|
-
properties: {} as Record<string, ILlmSchema>,
|
|
793
|
-
} satisfies ILlmSchema.IObject;
|
|
794
|
-
|
|
795
|
-
for (const [key, value] of Object.entries(props.schema.properties ?? {})) {
|
|
796
|
-
const [x, y] = separateStation({
|
|
797
|
-
predicate: props.predicate,
|
|
798
|
-
convention: props.convention,
|
|
799
|
-
$defs: props.$defs,
|
|
800
|
-
schema: value,
|
|
801
|
-
});
|
|
802
|
-
if (x !== null) llm.properties[key] = x;
|
|
803
|
-
if (y !== null) human.properties[key] = y;
|
|
804
|
-
}
|
|
805
|
-
if (
|
|
806
|
-
typeof props.schema.additionalProperties === "object" &&
|
|
807
|
-
props.schema.additionalProperties !== null
|
|
808
|
-
) {
|
|
809
|
-
const [dx, dy] = separateStation({
|
|
810
|
-
predicate: props.predicate,
|
|
811
|
-
convention: props.convention,
|
|
812
|
-
$defs: props.$defs,
|
|
813
|
-
schema: props.schema.additionalProperties,
|
|
814
|
-
});
|
|
815
|
-
llm.additionalProperties = dx ?? false;
|
|
816
|
-
human.additionalProperties = dy ?? false;
|
|
817
|
-
}
|
|
818
|
-
return [
|
|
819
|
-
!!Object.keys(llm.properties).length || !!llm.additionalProperties
|
|
820
|
-
? shrinkRequired(llm)
|
|
821
|
-
: null,
|
|
822
|
-
!!Object.keys(human.properties).length || human.additionalProperties
|
|
823
|
-
? shrinkRequired(human)
|
|
824
|
-
: null,
|
|
825
|
-
];
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
const separateReference = (props: {
|
|
829
|
-
predicate: (schema: ILlmSchema) => boolean;
|
|
830
|
-
convention: (key: string, type: "llm" | "human") => string;
|
|
831
|
-
$defs: Record<string, ILlmSchema>;
|
|
832
|
-
schema: ILlmSchema.IReference;
|
|
833
|
-
}): [ILlmSchema.IReference | null, ILlmSchema.IReference | null] => {
|
|
834
|
-
const key: string =
|
|
835
|
-
props.schema.$ref.split("#/$defs/")[1] ??
|
|
836
|
-
props.schema.$ref.split("/").at(-1)!;
|
|
837
|
-
const humanKey: string = props.convention(key, "human");
|
|
838
|
-
const llmKey: string = props.convention(key, "llm");
|
|
839
|
-
|
|
840
|
-
// FIND EXISTING
|
|
841
|
-
if (props.$defs?.[humanKey] || props.$defs?.[llmKey])
|
|
842
|
-
return [
|
|
843
|
-
props.$defs?.[llmKey]
|
|
844
|
-
? {
|
|
845
|
-
...props.schema,
|
|
846
|
-
$ref: `#/$defs/${llmKey}`,
|
|
847
|
-
}
|
|
848
|
-
: null,
|
|
849
|
-
props.$defs?.[humanKey]
|
|
850
|
-
? {
|
|
851
|
-
...props.schema,
|
|
852
|
-
$ref: `#/$defs/${humanKey}`,
|
|
853
|
-
}
|
|
854
|
-
: null,
|
|
855
|
-
];
|
|
856
|
-
|
|
857
|
-
// PRE-ASSIGNMENT
|
|
858
|
-
props.$defs![llmKey] = {};
|
|
859
|
-
props.$defs![humanKey] = {};
|
|
860
|
-
|
|
861
|
-
// DO COMPOSE
|
|
862
|
-
const schema: ILlmSchema = props.$defs?.[key]!;
|
|
863
|
-
const [llm, human] = separateStation({
|
|
864
|
-
predicate: props.predicate,
|
|
865
|
-
convention: props.convention,
|
|
866
|
-
$defs: props.$defs,
|
|
867
|
-
schema,
|
|
868
|
-
});
|
|
869
|
-
if (llm !== null) Object.assign(props.$defs[llmKey], llm);
|
|
870
|
-
if (human !== null) Object.assign(props.$defs[humanKey], human);
|
|
871
|
-
|
|
872
|
-
// ONLY ONE
|
|
873
|
-
if (llm === null || human === null) {
|
|
874
|
-
delete props.$defs[llmKey];
|
|
875
|
-
delete props.$defs[humanKey];
|
|
876
|
-
return llm === null ? [null, props.schema] : [props.schema, null];
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// BOTH OF THEM
|
|
880
|
-
return [
|
|
881
|
-
llm !== null
|
|
882
|
-
? {
|
|
883
|
-
...props.schema,
|
|
884
|
-
$ref: `#/$defs/${llmKey}`,
|
|
885
|
-
}
|
|
886
|
-
: null,
|
|
887
|
-
human !== null
|
|
888
|
-
? {
|
|
889
|
-
...props.schema,
|
|
890
|
-
$ref: `#/$defs/${humanKey}`,
|
|
891
|
-
}
|
|
892
|
-
: null,
|
|
893
|
-
];
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
const shrinkRequired = (s: ILlmSchema.IObject): ILlmSchema.IObject => {
|
|
897
|
-
s.required = s.required.filter((key) => s.properties?.[key] !== undefined);
|
|
898
|
-
return s;
|
|
899
|
-
};
|
|
900
623
|
}
|
|
901
624
|
|
|
902
625
|
const validateStrict = (
|
package/src/http/HttpLlm.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
IHttpLlmFunction,
|
|
6
6
|
IHttpMigrateApplication,
|
|
7
7
|
IHttpResponse,
|
|
8
|
-
ILlmFunction,
|
|
9
8
|
OpenApi,
|
|
10
9
|
OpenApiV3,
|
|
11
10
|
OpenApiV3_1,
|
|
@@ -15,7 +14,6 @@ import {
|
|
|
15
14
|
import { HttpMigration } from "./HttpMigration";
|
|
16
15
|
import { HttpLlmApplicationComposer } from "./internal/HttpLlmApplicationComposer";
|
|
17
16
|
import { HttpLlmFunctionFetcher } from "./internal/HttpLlmFunctionFetcher";
|
|
18
|
-
import { LlmDataMerger } from "./internal/LlmDataMerger";
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* LLM function calling utilities for OpenAPI documents.
|
|
@@ -92,8 +90,7 @@ export namespace HttpLlm {
|
|
|
92
90
|
/**
|
|
93
91
|
* Convert OpenAPI document to LLM function calling application.
|
|
94
92
|
*
|
|
95
|
-
* Converts API operations to LLM-callable functions.
|
|
96
|
-
* {@link mergeParameters} if `separate` option is configured.
|
|
93
|
+
* Converts API operations to LLM-callable functions.
|
|
97
94
|
*
|
|
98
95
|
* @param props Composition properties
|
|
99
96
|
* @returns LLM function calling application
|
|
@@ -118,7 +115,6 @@ export namespace HttpLlm {
|
|
|
118
115
|
config: {
|
|
119
116
|
reference: props.config?.reference ?? true,
|
|
120
117
|
strict: props.config?.strict ?? false,
|
|
121
|
-
separate: props.config?.separate ?? null,
|
|
122
118
|
maxLength: props.config?.maxLength ?? 64,
|
|
123
119
|
equals: props.config?.equals ?? false,
|
|
124
120
|
},
|
|
@@ -168,43 +164,4 @@ export namespace HttpLlm {
|
|
|
168
164
|
*/
|
|
169
165
|
export const propagate = (props: IFetchProps): Promise<IHttpResponse> =>
|
|
170
166
|
HttpLlmFunctionFetcher.propagate(props);
|
|
171
|
-
|
|
172
|
-
/* -----------------------------------------------------------
|
|
173
|
-
MERGERS
|
|
174
|
-
----------------------------------------------------------- */
|
|
175
|
-
/** Properties for parameter merging. */
|
|
176
|
-
export interface IMergeProps {
|
|
177
|
-
/** Target function metadata. */
|
|
178
|
-
function: ILlmFunction;
|
|
179
|
-
|
|
180
|
-
/** LLM-provided arguments. */
|
|
181
|
-
llm: object | null;
|
|
182
|
-
|
|
183
|
-
/** Human-provided arguments. */
|
|
184
|
-
human: object | null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Merge separated parameters.
|
|
189
|
-
*
|
|
190
|
-
* Combines human and LLM parameters when `separate` option was used. Throws
|
|
191
|
-
* error if `separate` was not configured.
|
|
192
|
-
*
|
|
193
|
-
* @param props Merge properties
|
|
194
|
-
* @returns Merged parameters
|
|
195
|
-
*/
|
|
196
|
-
export const mergeParameters = (props: IMergeProps): object =>
|
|
197
|
-
LlmDataMerger.parameters(props);
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Merge two values.
|
|
201
|
-
*
|
|
202
|
-
* Objects are merged at property level. Primitives return `y ?? x`.
|
|
203
|
-
*
|
|
204
|
-
* @param x First value
|
|
205
|
-
* @param y Second value (preferred)
|
|
206
|
-
* @returns Merged value
|
|
207
|
-
*/
|
|
208
|
-
export const mergeValue = (x: unknown, y: unknown): unknown =>
|
|
209
|
-
LlmDataMerger.value(x, y);
|
|
210
167
|
}
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
OpenApi,
|
|
10
10
|
} from "@typia/interface";
|
|
11
11
|
|
|
12
|
-
import { LlmSchemaConverter } from "../../converters";
|
|
12
|
+
import { LlmSchemaConverter } from "../../converters/LlmSchemaConverter";
|
|
13
|
+
import { LlmJson } from "../../utils";
|
|
13
14
|
import { OpenApiValidator } from "../../validators/OpenApiValidator";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -33,7 +34,6 @@ export namespace HttpLlmApplicationComposer {
|
|
|
33
34
|
}): IHttpLlmApplication => {
|
|
34
35
|
// fill in config defaults
|
|
35
36
|
const config: IHttpLlmApplication.IConfig = {
|
|
36
|
-
separate: props.config?.separate ?? null,
|
|
37
37
|
maxLength: props.config?.maxLength ?? 64,
|
|
38
38
|
equals: props.config?.equals ?? false,
|
|
39
39
|
reference: props.config?.reference ?? true,
|
|
@@ -256,17 +256,11 @@ export namespace HttpLlmApplicationComposer {
|
|
|
256
256
|
path: props.route.path,
|
|
257
257
|
name,
|
|
258
258
|
parameters: llmParameters.value,
|
|
259
|
-
separated: props.config.separate
|
|
260
|
-
? LlmSchemaConverter.separate({
|
|
261
|
-
predicate: props.config.separate,
|
|
262
|
-
parameters: llmParameters.value,
|
|
263
|
-
equals: props.config.equals ?? false,
|
|
264
|
-
})
|
|
265
|
-
: undefined,
|
|
266
259
|
output: output?.value,
|
|
267
260
|
description,
|
|
268
261
|
deprecated: operation.deprecated,
|
|
269
262
|
tags: operation.tags,
|
|
263
|
+
parse: (input: string) => LlmJson.parse(input, llmParameters.value),
|
|
270
264
|
validate: OpenApiValidator.create({
|
|
271
265
|
components: props.components,
|
|
272
266
|
schema: parameters,
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IJsonParseResult,
|
|
3
|
+
ILlmSchema,
|
|
4
|
+
IValidation,
|
|
5
|
+
OpenApi,
|
|
6
|
+
} from "@typia/interface";
|
|
7
|
+
|
|
8
|
+
import { LlmSchemaConverter } from "../converters";
|
|
9
|
+
import { OpenApiValidator } from "../validators";
|
|
10
|
+
import { coerceLlmArguments } from "./internal/coerceLlmArguments";
|
|
11
|
+
import { parseLenientJson } from "./internal/parseLenientJson";
|
|
12
|
+
import { stringifyValidationFailure } from "./internal/stringifyValidationFailure";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* JSON utilities for LLM function calling.
|
|
16
|
+
*
|
|
17
|
+
* - {@link LlmJson.parse}: Lenient JSON parser for incomplete/malformed JSON
|
|
18
|
+
* - {@link LlmJson.stringify}: Format validation errors for LLM feedback
|
|
19
|
+
* - {@link LlmJson.validate}: Create a reusable validator from schema
|
|
20
|
+
*
|
|
21
|
+
* @author Jeongho Nam - https://github.com/samchon
|
|
22
|
+
*/
|
|
23
|
+
export namespace LlmJson {
|
|
24
|
+
/**
|
|
25
|
+
* Parse lenient JSON with optional schema-based coercion.
|
|
26
|
+
*
|
|
27
|
+
* Handles incomplete/malformed JSON commonly produced by LLMs:
|
|
28
|
+
*
|
|
29
|
+
* - Unclosed brackets, strings, trailing commas
|
|
30
|
+
* - JavaScript-style comments (`//` and multi-line)
|
|
31
|
+
* - Unquoted object keys, incomplete keywords (`tru`, `fal`, `nul`)
|
|
32
|
+
* - Markdown code block extraction, junk prefix skipping
|
|
33
|
+
*
|
|
34
|
+
* When `parameters` schema is provided, also coerces double-stringified
|
|
35
|
+
* values: `"42"` → `42`, `"true"` → `true`, `"{...}"` → `{...}` based on
|
|
36
|
+
* expected types.
|
|
37
|
+
*
|
|
38
|
+
* Type validation is NOT performed - use {@link ILlmFunction.validate}.
|
|
39
|
+
*
|
|
40
|
+
* @param input Raw JSON string (potentially incomplete or malformed)
|
|
41
|
+
* @param parameters Optional LLM parameters schema for type coercion
|
|
42
|
+
* @returns Parse result with data on success, or partial data with errors
|
|
43
|
+
*/
|
|
44
|
+
export function parse<T = unknown>(
|
|
45
|
+
input: string,
|
|
46
|
+
parameters?: ILlmSchema.IParameters,
|
|
47
|
+
): IJsonParseResult<T> {
|
|
48
|
+
const result: IJsonParseResult<T> = parseLenientJson<T>(input);
|
|
49
|
+
|
|
50
|
+
// Apply schema-based coercion if parameters provided and parsing succeeded
|
|
51
|
+
if (parameters !== undefined && result.success) {
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
data: coerceLlmArguments(result.data, parameters) as T,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format validation failure for LLM auto-correction feedback.
|
|
62
|
+
*
|
|
63
|
+
* When LLM generates invalid function call arguments, this produces annotated
|
|
64
|
+
* JSON with inline `// ❌` error comments at each invalid property. The output
|
|
65
|
+
* is wrapped in a markdown code block so that LLM can understand and correct
|
|
66
|
+
* its mistakes in the next turn.
|
|
67
|
+
*
|
|
68
|
+
* Below is an example of the output format:
|
|
69
|
+
*
|
|
70
|
+
* ```json
|
|
71
|
+
* {
|
|
72
|
+
* "name": "John",
|
|
73
|
+
* "age": "twenty", // ❌ [{"path":"$input.age","expected":"number & Type<\"uint32\">"}]
|
|
74
|
+
* "email": "not-an-email", // ❌ [{"path":"$input.email","expected":"string & Format<\"email\">"}]
|
|
75
|
+
* "hobbies": "reading" // ❌ [{"path":"$input.hobbies","expected":"Array<string>"}]
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @param failure Validation failure from {@link ILlmFunction.validate}
|
|
80
|
+
*/
|
|
81
|
+
export function stringify(failure: IValidation.IFailure): string {
|
|
82
|
+
return stringifyValidationFailure(failure);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a reusable validator from LLM parameters schema.
|
|
87
|
+
*
|
|
88
|
+
* When validation fails, format the failure with {@link stringify} for LLM
|
|
89
|
+
* auto-correction feedback.
|
|
90
|
+
*
|
|
91
|
+
* @param parameters LLM function parameters schema
|
|
92
|
+
* @param equals If `true`, reject extraneous properties not defined in the
|
|
93
|
+
* schema. Otherwise, extra properties are ignored.
|
|
94
|
+
* @returns Validator function that checks data against the schema
|
|
95
|
+
*/
|
|
96
|
+
export function validate(
|
|
97
|
+
parameters: ILlmSchema.IParameters,
|
|
98
|
+
equals?: boolean | undefined,
|
|
99
|
+
) {
|
|
100
|
+
const components: OpenApi.IComponents = {
|
|
101
|
+
schemas: {},
|
|
102
|
+
};
|
|
103
|
+
const schema: OpenApi.IJsonSchema = LlmSchemaConverter.invert({
|
|
104
|
+
components,
|
|
105
|
+
schema: parameters,
|
|
106
|
+
$defs: parameters.$defs,
|
|
107
|
+
});
|
|
108
|
+
return OpenApiValidator.create({
|
|
109
|
+
components,
|
|
110
|
+
schema,
|
|
111
|
+
required: true,
|
|
112
|
+
equals,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from "./ArrayUtil";
|
|
2
|
+
export * from "./LlmJson";
|
|
2
3
|
export * from "./MapUtil";
|
|
3
4
|
export * from "./NamingConvention";
|
|
4
5
|
export * from "./Singleton";
|
|
5
6
|
export * from "./StringUtil";
|
|
6
7
|
|
|
7
8
|
export * from "./dedent";
|
|
8
|
-
export * from "./stringifyValidationFailure";
|