@typia/langchain 13.1.1 → 13.2.0
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/lib/index.d.cts +66 -0
- package/lib/index.d.mts +66 -0
- package/lib/internal/LangChainParameterConverter.d.ts +37 -0
- package/lib/internal/LangChainParameterConverter.js +54 -0
- package/lib/internal/LangChainParameterConverter.js.map +1 -0
- package/lib/internal/LangChainParameterConverter.mjs +19 -0
- package/lib/internal/LangChainParameterConverter.mjs.map +1 -0
- package/lib/internal/LangChainToolsRegistrar.js +73 -39
- package/lib/internal/LangChainToolsRegistrar.js.map +1 -1
- package/lib/internal/LangChainToolsRegistrar.mjs +52 -30
- package/lib/internal/LangChainToolsRegistrar.mjs.map +1 -1
- package/package.json +15 -7
- package/src/internal/LangChainParameterConverter.ts +55 -0
- package/src/internal/LangChainToolsRegistrar.ts +66 -15
package/lib/index.d.cts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
2
|
+
import { IHttpLlmController, ILlmController } from "@typia/interface";
|
|
3
|
+
type ILangChainController = ILlmController | IHttpLlmController;
|
|
4
|
+
interface ILangChainToolsOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to add controller name as prefix to tool names.
|
|
7
|
+
*
|
|
8
|
+
* If `true`, tool names become `{controllerName}_{methodName}`. If `false`,
|
|
9
|
+
* tool names are just `{methodName}`.
|
|
10
|
+
*
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
prefix?: boolean | undefined;
|
|
14
|
+
}
|
|
15
|
+
interface ILangChainToolsProps extends ILangChainToolsOptions {
|
|
16
|
+
/**
|
|
17
|
+
* List of controllers to convert to LangChain tools.
|
|
18
|
+
*
|
|
19
|
+
* - {@link ILlmController}: from `typia.llm.controller<Class>()`, converts all
|
|
20
|
+
* methods of the class to tools
|
|
21
|
+
* - {@link IHttpLlmController}: from `HttpLlm.controller()`, converts all
|
|
22
|
+
* operations from OpenAPI document to tools
|
|
23
|
+
*/
|
|
24
|
+
controllers: ILangChainController[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Convert typia controllers to LangChain tools.
|
|
28
|
+
*
|
|
29
|
+
* Converts TypeScript class methods via `typia.llm.controller<Class>()` or
|
|
30
|
+
* OpenAPI operations via `HttpLlm.controller()` to LangChain tools.
|
|
31
|
+
*
|
|
32
|
+
* Every tool call is validated by typia. If LLM provides invalid arguments,
|
|
33
|
+
* returns validation error formatted by {@link LlmJson.stringify} so that LLM
|
|
34
|
+
* can correct them automatically.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { initChatModel } from "langchain/chat_models/universal";
|
|
39
|
+
* import typia from "typia";
|
|
40
|
+
* import { toLangChainTools } from "@typia/langchain";
|
|
41
|
+
*
|
|
42
|
+
* class Calculator {
|
|
43
|
+
* add(input: { a: number; b: number }): { value: number } {
|
|
44
|
+
* return { value: input.a + input.b };
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* const controller = typia.llm.controller<Calculator>(
|
|
49
|
+
* "calculator",
|
|
50
|
+
* new Calculator(),
|
|
51
|
+
* );
|
|
52
|
+
* const tools = toLangChainTools(controller);
|
|
53
|
+
*
|
|
54
|
+
* const llm = await initChatModel();
|
|
55
|
+
* const modelWithTools = llm.bindTools(tools);
|
|
56
|
+
* const result = await modelWithTools.invoke("What is 10 + 5?");
|
|
57
|
+
* ```;
|
|
58
|
+
*
|
|
59
|
+
* @param input Controller, controller list, or conversion properties
|
|
60
|
+
* @param options Conversion options when `input` is not a properties object
|
|
61
|
+
* @returns Array of LangChain DynamicStructuredTool
|
|
62
|
+
*/
|
|
63
|
+
export declare function toLangChainTools(controller: ILangChainController, options?: ILangChainToolsOptions | undefined): DynamicStructuredTool[];
|
|
64
|
+
export declare function toLangChainTools(controllers: ILangChainController[], options?: ILangChainToolsOptions | undefined): DynamicStructuredTool[];
|
|
65
|
+
export declare function toLangChainTools(props: ILangChainToolsProps): DynamicStructuredTool[];
|
|
66
|
+
export {};
|
package/lib/index.d.mts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { DynamicStructuredTool } from "@langchain/core/tools";
|
|
2
|
+
import { IHttpLlmController, ILlmController } from "@typia/interface";
|
|
3
|
+
type ILangChainController = ILlmController | IHttpLlmController;
|
|
4
|
+
interface ILangChainToolsOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to add controller name as prefix to tool names.
|
|
7
|
+
*
|
|
8
|
+
* If `true`, tool names become `{controllerName}_{methodName}`. If `false`,
|
|
9
|
+
* tool names are just `{methodName}`.
|
|
10
|
+
*
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
prefix?: boolean | undefined;
|
|
14
|
+
}
|
|
15
|
+
interface ILangChainToolsProps extends ILangChainToolsOptions {
|
|
16
|
+
/**
|
|
17
|
+
* List of controllers to convert to LangChain tools.
|
|
18
|
+
*
|
|
19
|
+
* - {@link ILlmController}: from `typia.llm.controller<Class>()`, converts all
|
|
20
|
+
* methods of the class to tools
|
|
21
|
+
* - {@link IHttpLlmController}: from `HttpLlm.controller()`, converts all
|
|
22
|
+
* operations from OpenAPI document to tools
|
|
23
|
+
*/
|
|
24
|
+
controllers: ILangChainController[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Convert typia controllers to LangChain tools.
|
|
28
|
+
*
|
|
29
|
+
* Converts TypeScript class methods via `typia.llm.controller<Class>()` or
|
|
30
|
+
* OpenAPI operations via `HttpLlm.controller()` to LangChain tools.
|
|
31
|
+
*
|
|
32
|
+
* Every tool call is validated by typia. If LLM provides invalid arguments,
|
|
33
|
+
* returns validation error formatted by {@link LlmJson.stringify} so that LLM
|
|
34
|
+
* can correct them automatically.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { initChatModel } from "langchain/chat_models/universal";
|
|
39
|
+
* import typia from "typia";
|
|
40
|
+
* import { toLangChainTools } from "@typia/langchain";
|
|
41
|
+
*
|
|
42
|
+
* class Calculator {
|
|
43
|
+
* add(input: { a: number; b: number }): { value: number } {
|
|
44
|
+
* return { value: input.a + input.b };
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* const controller = typia.llm.controller<Calculator>(
|
|
49
|
+
* "calculator",
|
|
50
|
+
* new Calculator(),
|
|
51
|
+
* );
|
|
52
|
+
* const tools = toLangChainTools(controller);
|
|
53
|
+
*
|
|
54
|
+
* const llm = await initChatModel();
|
|
55
|
+
* const modelWithTools = llm.bindTools(tools);
|
|
56
|
+
* const result = await modelWithTools.invoke("What is 10 + 5?");
|
|
57
|
+
* ```;
|
|
58
|
+
*
|
|
59
|
+
* @param input Controller, controller list, or conversion properties
|
|
60
|
+
* @param options Conversion options when `input` is not a properties object
|
|
61
|
+
* @returns Array of LangChain DynamicStructuredTool
|
|
62
|
+
*/
|
|
63
|
+
export declare function toLangChainTools(controller: ILangChainController, options?: ILangChainToolsOptions | undefined): DynamicStructuredTool[];
|
|
64
|
+
export declare function toLangChainTools(controllers: ILangChainController[], options?: ILangChainToolsOptions | undefined): DynamicStructuredTool[];
|
|
65
|
+
export declare function toLangChainTools(props: ILangChainToolsProps): DynamicStructuredTool[];
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { IHttpLlmFunction, ILlmFunction } from "@typia/interface";
|
|
3
|
+
/**
|
|
4
|
+
* A Standard JSON Schema view of one typia function's parameters.
|
|
5
|
+
*
|
|
6
|
+
* LangChain's tool `schema` fills two roles at once:
|
|
7
|
+
* `toJsonSchema(tool.schema)` turns it into the parameters the model is shown,
|
|
8
|
+
* and `StructuredTool.call` validates incoming arguments against it before the
|
|
9
|
+
* tool body runs. Handing over a bare `ILlmSchema.IParameters` fills the first
|
|
10
|
+
* role but forfeits the second — LangChain validates it with
|
|
11
|
+
* `@cfworker/json-schema`, which rejects what typia would have coerced and
|
|
12
|
+
* reports the failure in its own words, so typia's coercion and its correctable
|
|
13
|
+
* feedback both become unreachable.
|
|
14
|
+
*
|
|
15
|
+
* Standard JSON Schema separates the two roles. It carries a model-facing
|
|
16
|
+
* schema under `~standard.jsonSchema` and, by declaring no
|
|
17
|
+
* `~standard.validate`, no validator — which is exactly typia's position: show
|
|
18
|
+
* the model this schema, and leave validation to `LlmJson.validateArguments` in
|
|
19
|
+
* the tool body. LangChain reads the schema back through its own
|
|
20
|
+
* `toJsonSchema`, so the model sees the same parameters document as before,
|
|
21
|
+
* byte for byte.
|
|
22
|
+
*
|
|
23
|
+
* `@typia/vercel` states the same contract to the AI SDK through
|
|
24
|
+
* `jsonSchema()`, whose `validate` is likewise left undefined so that
|
|
25
|
+
* `safeValidateTypes` short-circuits its own validation.
|
|
26
|
+
*
|
|
27
|
+
* Two version floors hold this together, and both are pinned in the package
|
|
28
|
+
* manifest. `toJsonSchema` only reads `~standard.jsonSchema` from
|
|
29
|
+
* `@langchain/core@1.1.30` onwards — older versions return the wrapper itself
|
|
30
|
+
* and would advertise _that_ to the model in place of the parameters — and
|
|
31
|
+
* `StandardJSONSchemaV1` only exists from `@standard-schema/spec@1.1.0`.
|
|
32
|
+
*
|
|
33
|
+
* @see https://github.com/standard-schema/standard-schema
|
|
34
|
+
*/
|
|
35
|
+
export declare namespace LangChainParameterConverter {
|
|
36
|
+
const convert: (func: ILlmFunction | IHttpLlmFunction) => StandardJSONSchemaV1;
|
|
37
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LangChainParameterConverter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A Standard JSON Schema view of one typia function's parameters.
|
|
6
|
+
*
|
|
7
|
+
* LangChain's tool `schema` fills two roles at once:
|
|
8
|
+
* `toJsonSchema(tool.schema)` turns it into the parameters the model is shown,
|
|
9
|
+
* and `StructuredTool.call` validates incoming arguments against it before the
|
|
10
|
+
* tool body runs. Handing over a bare `ILlmSchema.IParameters` fills the first
|
|
11
|
+
* role but forfeits the second — LangChain validates it with
|
|
12
|
+
* `@cfworker/json-schema`, which rejects what typia would have coerced and
|
|
13
|
+
* reports the failure in its own words, so typia's coercion and its correctable
|
|
14
|
+
* feedback both become unreachable.
|
|
15
|
+
*
|
|
16
|
+
* Standard JSON Schema separates the two roles. It carries a model-facing
|
|
17
|
+
* schema under `~standard.jsonSchema` and, by declaring no
|
|
18
|
+
* `~standard.validate`, no validator — which is exactly typia's position: show
|
|
19
|
+
* the model this schema, and leave validation to `LlmJson.validateArguments` in
|
|
20
|
+
* the tool body. LangChain reads the schema back through its own
|
|
21
|
+
* `toJsonSchema`, so the model sees the same parameters document as before,
|
|
22
|
+
* byte for byte.
|
|
23
|
+
*
|
|
24
|
+
* `@typia/vercel` states the same contract to the AI SDK through
|
|
25
|
+
* `jsonSchema()`, whose `validate` is likewise left undefined so that
|
|
26
|
+
* `safeValidateTypes` short-circuits its own validation.
|
|
27
|
+
*
|
|
28
|
+
* Two version floors hold this together, and both are pinned in the package
|
|
29
|
+
* manifest. `toJsonSchema` only reads `~standard.jsonSchema` from
|
|
30
|
+
* `@langchain/core@1.1.30` onwards — older versions return the wrapper itself
|
|
31
|
+
* and would advertise _that_ to the model in place of the parameters — and
|
|
32
|
+
* `StandardJSONSchemaV1` only exists from `@standard-schema/spec@1.1.0`.
|
|
33
|
+
*
|
|
34
|
+
* @see https://github.com/standard-schema/standard-schema
|
|
35
|
+
*/
|
|
36
|
+
var LangChainParameterConverter;
|
|
37
|
+
(function (LangChainParameterConverter) {
|
|
38
|
+
LangChainParameterConverter.convert = (func) => {
|
|
39
|
+
// Coercion never changes the shape a value must end up in, so the input and
|
|
40
|
+
// output schemas are the same document.
|
|
41
|
+
const jsonSchema = () => func.parameters;
|
|
42
|
+
return {
|
|
43
|
+
"~standard": {
|
|
44
|
+
version: 1,
|
|
45
|
+
vendor: "typia",
|
|
46
|
+
jsonSchema: {
|
|
47
|
+
input: jsonSchema,
|
|
48
|
+
output: jsonSchema,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
})(LangChainParameterConverter || (exports.LangChainParameterConverter = LangChainParameterConverter = {}));
|
|
54
|
+
//# sourceMappingURL=LangChainParameterConverter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LangChainParameterConverter.js","sourceRoot":"","sources":["../../src/internal/LangChainParameterConverter.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,IAAiB,2BAA2B,CAmB3C;AAnBD,WAAiB,2BAA2B;IAC7B,mCAAO,GAAG,CACrB,IAAqC,EACf,EAAE;QACxB,4EAA4E;QAC5E,wCAAwC;QACxC,MAAM,UAAU,GAAG,GAA4B,EAAE,CAC/C,IAAI,CAAC,UAAgD,CAAC;QACxD,OAAO;YACL,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,OAAO;gBACf,UAAU,EAAE;oBACV,KAAK,EAAE,UAAU;oBACjB,MAAM,EAAE,UAAU;iBACnB;aACF;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC,EAnBgB,2BAA2B,aAA3B,2BAA2B,GAA3B,2BAA2B,QAmB3C"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/internal/LangChainParameterConverter.ts
|
|
2
|
+
let LangChainParameterConverter;
|
|
3
|
+
(function(_LangChainParameterConverter) {
|
|
4
|
+
_LangChainParameterConverter.convert = (func) => {
|
|
5
|
+
const jsonSchema = () => func.parameters;
|
|
6
|
+
return { "~standard": {
|
|
7
|
+
version: 1,
|
|
8
|
+
vendor: "typia",
|
|
9
|
+
jsonSchema: {
|
|
10
|
+
input: jsonSchema,
|
|
11
|
+
output: jsonSchema
|
|
12
|
+
}
|
|
13
|
+
} };
|
|
14
|
+
};
|
|
15
|
+
})(LangChainParameterConverter || (LangChainParameterConverter = {}));
|
|
16
|
+
//#endregion
|
|
17
|
+
export { LangChainParameterConverter };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=LangChainParameterConverter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LangChainParameterConverter.mjs","names":[],"sources":["../../src/internal/LangChainParameterConverter.ts"],"sourcesContent":["import { StandardJSONSchemaV1 } from \"@standard-schema/spec\";\nimport { IHttpLlmFunction, ILlmFunction } from \"@typia/interface\";\n\n/**\n * A Standard JSON Schema view of one typia function's parameters.\n *\n * LangChain's tool `schema` fills two roles at once:\n * `toJsonSchema(tool.schema)` turns it into the parameters the model is shown,\n * and `StructuredTool.call` validates incoming arguments against it before the\n * tool body runs. Handing over a bare `ILlmSchema.IParameters` fills the first\n * role but forfeits the second — LangChain validates it with\n * `@cfworker/json-schema`, which rejects what typia would have coerced and\n * reports the failure in its own words, so typia's coercion and its correctable\n * feedback both become unreachable.\n *\n * Standard JSON Schema separates the two roles. It carries a model-facing\n * schema under `~standard.jsonSchema` and, by declaring no\n * `~standard.validate`, no validator — which is exactly typia's position: show\n * the model this schema, and leave validation to `LlmJson.validateArguments` in\n * the tool body. LangChain reads the schema back through its own\n * `toJsonSchema`, so the model sees the same parameters document as before,\n * byte for byte.\n *\n * `@typia/vercel` states the same contract to the AI SDK through\n * `jsonSchema()`, whose `validate` is likewise left undefined so that\n * `safeValidateTypes` short-circuits its own validation.\n *\n * Two version floors hold this together, and both are pinned in the package\n * manifest. `toJsonSchema` only reads `~standard.jsonSchema` from\n * `@langchain/core@1.1.30` onwards — older versions return the wrapper itself\n * and would advertise _that_ to the model in place of the parameters — and\n * `StandardJSONSchemaV1` only exists from `@standard-schema/spec@1.1.0`.\n *\n * @see https://github.com/standard-schema/standard-schema\n */\nexport namespace LangChainParameterConverter {\n export const convert = (\n func: ILlmFunction | IHttpLlmFunction,\n ): StandardJSONSchemaV1 => {\n // Coercion never changes the shape a value must end up in, so the input and\n // output schemas are the same document.\n const jsonSchema = (): Record<string, unknown> =>\n func.parameters as unknown as Record<string, unknown>;\n return {\n \"~standard\": {\n version: 1,\n vendor: \"typia\",\n jsonSchema: {\n input: jsonSchema,\n output: jsonSchema,\n },\n },\n };\n };\n}\n"],"mappings":";AAmCO,IAAA;;yCAEH,SACyB;EAGzB,MAAM,mBACJ,KAAK;EACP,OAAO,EACL,aAAa;GACX,SAAS;GACT,QAAQ;GACR,YAAY;IACV,OAAO;IACP,QAAQ;GACV;EACF,EACF;CACF;GACD,gCAAA,8BAAA,CAAA,EAAD"}
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.LangChainToolsRegistrar = void 0;
|
|
13
13
|
const tools_1 = require("@langchain/core/tools");
|
|
14
14
|
const utils_1 = require("@typia/utils");
|
|
15
|
+
const LangChainParameterConverter_1 = require("./LangChainParameterConverter");
|
|
15
16
|
var LangChainToolsRegistrar;
|
|
16
17
|
(function (LangChainToolsRegistrar) {
|
|
17
18
|
LangChainToolsRegistrar.convert = (props) => {
|
|
@@ -19,16 +20,18 @@ var LangChainToolsRegistrar;
|
|
|
19
20
|
const prefix = (_a = props.prefix) !== null && _a !== void 0 ? _a : false;
|
|
20
21
|
const tools = [];
|
|
21
22
|
// check duplicate tool names
|
|
22
|
-
if (
|
|
23
|
+
if (props.controllers.length >= 2) {
|
|
23
24
|
const names = new Map();
|
|
24
25
|
const duplicates = [];
|
|
25
26
|
for (const controller of props.controllers) {
|
|
26
27
|
for (const func of controller.application.functions) {
|
|
27
|
-
const
|
|
28
|
+
const toolName = getToolName(controller.name, func.name, prefix);
|
|
29
|
+
const existing = names.get(toolName);
|
|
30
|
+
const origin = `${controller.protocol} controller "${controller.name}" function "${func.name}"`;
|
|
28
31
|
if (existing !== undefined)
|
|
29
|
-
duplicates.push(`"${
|
|
32
|
+
duplicates.push(`"${toolName}" from ${origin} (conflicts with ${existing})`);
|
|
30
33
|
else
|
|
31
|
-
names.set(
|
|
34
|
+
names.set(toolName, origin);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
if (duplicates.length > 0)
|
|
@@ -48,9 +51,7 @@ var LangChainToolsRegistrar;
|
|
|
48
51
|
const convertClassController = (tools, controller, prefix) => {
|
|
49
52
|
const execute = controller.execute;
|
|
50
53
|
for (const func of controller.application.functions) {
|
|
51
|
-
const toolName = prefix
|
|
52
|
-
? `${controller.name}_${func.name}`
|
|
53
|
-
: func.name;
|
|
54
|
+
const toolName = getToolName(controller.name, func.name, prefix);
|
|
54
55
|
const method = execute[func.name];
|
|
55
56
|
if (typeof method !== "function") {
|
|
56
57
|
throw new Error(`Method "${func.name}" not found on controller "${controller.name}"`);
|
|
@@ -58,6 +59,7 @@ var LangChainToolsRegistrar;
|
|
|
58
59
|
tools.push(createTool({
|
|
59
60
|
name: toolName,
|
|
60
61
|
function: func,
|
|
62
|
+
config: controller.application.config,
|
|
61
63
|
execute: (args) => __awaiter(this, void 0, void 0, function* () { return method.call(execute, args); }),
|
|
62
64
|
}));
|
|
63
65
|
}
|
|
@@ -66,12 +68,11 @@ var LangChainToolsRegistrar;
|
|
|
66
68
|
const application = controller.application;
|
|
67
69
|
const connection = controller.connection;
|
|
68
70
|
for (const func of application.functions) {
|
|
69
|
-
const toolName = prefix
|
|
70
|
-
? `${controller.name}_${func.name}`
|
|
71
|
-
: func.name;
|
|
71
|
+
const toolName = getToolName(controller.name, func.name, prefix);
|
|
72
72
|
tools.push(createTool({
|
|
73
73
|
name: toolName,
|
|
74
74
|
function: func,
|
|
75
|
+
config: application.config,
|
|
75
76
|
execute: (args) => __awaiter(this, void 0, void 0, function* () {
|
|
76
77
|
if (controller.execute !== undefined) {
|
|
77
78
|
const response = yield controller.execute({
|
|
@@ -92,35 +93,68 @@ var LangChainToolsRegistrar;
|
|
|
92
93
|
}));
|
|
93
94
|
}
|
|
94
95
|
};
|
|
95
|
-
const createTool = (entry) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
const createTool = (entry) => {
|
|
97
|
+
var _a;
|
|
98
|
+
// A declared return type is a promise to the model, so a result that breaks
|
|
99
|
+
// it is reported rather than delivered. `@typia/mcp` and `@typia/vercel`
|
|
100
|
+
// already refuse one; this tool consulted `output` for its existence alone,
|
|
101
|
+
// so a method returning nothing was reported while a method returning the
|
|
102
|
+
// wrong shape was not (#2302).
|
|
103
|
+
//
|
|
104
|
+
// The application's own config decides how the schema is read back: a
|
|
105
|
+
// strict application carries its constraints as description tags instead of
|
|
106
|
+
// keywords, and only that config tells the inverter to read them.
|
|
107
|
+
const validateOutput = entry.function.output === undefined
|
|
108
|
+
? undefined
|
|
109
|
+
: utils_1.LlmJson.validate(entry.function.output, true, entry.config);
|
|
110
|
+
return (0, tools_1.tool)((args) => __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
const valid = utils_1.LlmJson.validateArguments(entry.function, args !== null && args !== void 0 ? args : {});
|
|
112
|
+
if (valid.success === false)
|
|
113
|
+
throw new tools_1.ToolInputParsingException(`Type errors in "${entry.name}" arguments:\n\n` +
|
|
114
|
+
utils_1.LlmJson.stringify(valid), JSON.stringify(args !== null && args !== void 0 ? args : {}));
|
|
115
|
+
try {
|
|
116
|
+
const result = yield entry.execute(valid.data);
|
|
117
|
+
if (result === undefined) {
|
|
118
|
+
return validateOutput === undefined
|
|
119
|
+
? { success: true }
|
|
120
|
+
: {
|
|
121
|
+
success: false,
|
|
122
|
+
error: `Function "${entry.name}" returned undefined despite declaring an output schema`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (validateOutput !== undefined) {
|
|
126
|
+
const output = validateOutput(result);
|
|
127
|
+
if (output.success === false)
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: `Type errors in "${entry.name}" output:
|
|
131
|
+
|
|
132
|
+
` + utils_1.LlmJson.stringify(output),
|
|
133
|
+
};
|
|
134
|
+
return { success: true, data: output.data };
|
|
135
|
+
}
|
|
136
|
+
return { success: true, data: result };
|
|
109
137
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: error instanceof Error
|
|
142
|
+
? `${error.name}: ${error.message}`
|
|
143
|
+
: String(error),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}), {
|
|
147
|
+
name: entry.name,
|
|
148
|
+
description: (_a = entry.function.description) !== null && _a !== void 0 ? _a : "",
|
|
149
|
+
// Declares the model-facing schema without a validator, so the
|
|
150
|
+
// coerce-and-validate step above is the only one that runs; see
|
|
151
|
+
// `LangChainParameterConverter`. The cast is needed because `tool()`
|
|
152
|
+
// still types `schema` as Zod-or-JSON-Schema, even though the
|
|
153
|
+
// `toJsonSchema` it reads that schema back with accepts Standard JSON
|
|
154
|
+
// Schema by its own public signature.
|
|
155
|
+
schema: LangChainParameterConverter_1.LangChainParameterConverter.convert(entry.function),
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
const getToolName = (controllerName, functionName, prefix) => (prefix ? `${controllerName}_${functionName}` : functionName);
|
|
125
159
|
})(LangChainToolsRegistrar || (exports.LangChainToolsRegistrar = LangChainToolsRegistrar = {}));
|
|
126
160
|
//# sourceMappingURL=LangChainToolsRegistrar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LangChainToolsRegistrar.js","sourceRoot":"","sources":["../../src/internal/LangChainToolsRegistrar.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,iDAI+B;
|
|
1
|
+
{"version":3,"file":"LangChainToolsRegistrar.js","sourceRoot":"","sources":["../../src/internal/LangChainToolsRegistrar.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LangChainParameterConverter } from "./LangChainParameterConverter.mjs";
|
|
1
2
|
import { ToolInputParsingException, tool } from "@langchain/core/tools";
|
|
2
3
|
import { HttpLlm, LlmJson } from "@typia/utils";
|
|
3
4
|
//#region src/internal/LangChainToolsRegistrar.ts
|
|
@@ -6,13 +7,15 @@ let LangChainToolsRegistrar;
|
|
|
6
7
|
_LangChainToolsRegistrar.convert = (props) => {
|
|
7
8
|
const prefix = props.prefix ?? false;
|
|
8
9
|
const tools = [];
|
|
9
|
-
if (
|
|
10
|
+
if (props.controllers.length >= 2) {
|
|
10
11
|
const names = /* @__PURE__ */ new Map();
|
|
11
12
|
const duplicates = [];
|
|
12
13
|
for (const controller of props.controllers) for (const func of controller.application.functions) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const toolName = getToolName(controller.name, func.name, prefix);
|
|
15
|
+
const existing = names.get(toolName);
|
|
16
|
+
const origin = `${controller.protocol} controller "${controller.name}" function "${func.name}"`;
|
|
17
|
+
if (existing !== void 0) duplicates.push(`"${toolName}" from ${origin} (conflicts with ${existing})`);
|
|
18
|
+
else names.set(toolName, origin);
|
|
16
19
|
}
|
|
17
20
|
if (duplicates.length > 0) throw new Error(`Duplicate tool names found:\n - ${duplicates.join("\n - ")}`);
|
|
18
21
|
}
|
|
@@ -23,12 +26,13 @@ let LangChainToolsRegistrar;
|
|
|
23
26
|
const convertClassController = (tools, controller, prefix) => {
|
|
24
27
|
const execute = controller.execute;
|
|
25
28
|
for (const func of controller.application.functions) {
|
|
26
|
-
const toolName =
|
|
29
|
+
const toolName = getToolName(controller.name, func.name, prefix);
|
|
27
30
|
const method = execute[func.name];
|
|
28
31
|
if (typeof method !== "function") throw new Error(`Method "${func.name}" not found on controller "${controller.name}"`);
|
|
29
32
|
tools.push(createTool({
|
|
30
33
|
name: toolName,
|
|
31
34
|
function: func,
|
|
35
|
+
config: controller.application.config,
|
|
32
36
|
execute: async (args) => method.call(execute, args)
|
|
33
37
|
}));
|
|
34
38
|
}
|
|
@@ -37,10 +41,11 @@ let LangChainToolsRegistrar;
|
|
|
37
41
|
const application = controller.application;
|
|
38
42
|
const connection = controller.connection;
|
|
39
43
|
for (const func of application.functions) {
|
|
40
|
-
const toolName =
|
|
44
|
+
const toolName = getToolName(controller.name, func.name, prefix);
|
|
41
45
|
tools.push(createTool({
|
|
42
46
|
name: toolName,
|
|
43
47
|
function: func,
|
|
48
|
+
config: application.config,
|
|
44
49
|
execute: async (args) => {
|
|
45
50
|
if (controller.execute !== void 0) return (await controller.execute({
|
|
46
51
|
connection,
|
|
@@ -58,30 +63,47 @@ let LangChainToolsRegistrar;
|
|
|
58
63
|
}));
|
|
59
64
|
}
|
|
60
65
|
};
|
|
61
|
-
const createTool = (entry) =>
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
const createTool = (entry) => {
|
|
67
|
+
const validateOutput = entry.function.output === void 0 ? void 0 : LlmJson.validate(entry.function.output, true, entry.config);
|
|
68
|
+
return tool(async (args) => {
|
|
69
|
+
const valid = LlmJson.validateArguments(entry.function, args ?? {});
|
|
70
|
+
if (valid.success === false) throw new ToolInputParsingException(`Type errors in "${entry.name}" arguments:\n\n` + LlmJson.stringify(valid), JSON.stringify(args ?? {}));
|
|
71
|
+
try {
|
|
72
|
+
const result = await entry.execute(valid.data);
|
|
73
|
+
if (result === void 0) return validateOutput === void 0 ? { success: true } : {
|
|
74
|
+
success: false,
|
|
75
|
+
error: `Function "${entry.name}" returned undefined despite declaring an output schema`
|
|
76
|
+
};
|
|
77
|
+
if (validateOutput !== void 0) {
|
|
78
|
+
const output = validateOutput(result);
|
|
79
|
+
if (output.success === false) return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: `Type errors in "${entry.name}" output:
|
|
82
|
+
|
|
83
|
+
` + LlmJson.stringify(output)
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
data: output.data
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
data: result
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: error instanceof Error ? `${error.name}: ${error.message}` : String(error)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}, {
|
|
101
|
+
name: entry.name,
|
|
102
|
+
description: entry.function.description ?? "",
|
|
103
|
+
schema: LangChainParameterConverter.convert(entry.function)
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const getToolName = (controllerName, functionName, prefix) => prefix ? `${controllerName}_${functionName}` : functionName;
|
|
85
107
|
})(LangChainToolsRegistrar || (LangChainToolsRegistrar = {}));
|
|
86
108
|
//#endregion
|
|
87
109
|
export { LangChainToolsRegistrar };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LangChainToolsRegistrar.mjs","names":[],"sources":["../../src/internal/LangChainToolsRegistrar.ts"],"sourcesContent":["import {\n DynamicStructuredTool,\n ToolInputParsingException,\n tool,\n} from \"@langchain/core/tools\";\nimport {\n IHttpLlmController,\n IHttpLlmFunction,\n ILlmController,\n ILlmFunction,\n IValidation,\n} from \"@typia/interface\";\nimport { HttpLlm, LlmJson } from \"@typia/utils\";\n\nexport namespace LangChainToolsRegistrar {\n export const convert = (props: {\n controllers: Array<ILlmController | IHttpLlmController>;\n prefix?: boolean | undefined;\n }): DynamicStructuredTool[] => {\n const prefix: boolean = props.prefix ?? false;\n const tools: DynamicStructuredTool[] = [];\n\n // check duplicate tool names\n if (prefix === false && props.controllers.length >= 2) {\n const names: Map<string, string> = new Map();\n const duplicates: string[] = [];\n for (const controller of props.controllers) {\n for (const func of controller.application.functions) {\n const existing: string | undefined = names.get(func.name);\n if (existing !== undefined)\n duplicates.push(\n `\"${func.name}\" in \"${controller.name}\" (conflicts with \"${existing}\")`,\n );\n else names.set(func.name, controller.name);\n }\n }\n if (duplicates.length > 0)\n throw new Error(\n `Duplicate tool names found:\\n - ${duplicates.join(\"\\n - \")}`,\n );\n }\n\n // convert controllers to tools\n for (const controller of props.controllers) {\n if (controller.protocol === \"class\") {\n convertClassController(tools, controller, prefix);\n } else {\n convertHttpController(tools, controller, prefix);\n }\n }\n\n return tools;\n };\n\n const convertClassController = (\n tools: DynamicStructuredTool[],\n controller: ILlmController,\n prefix: boolean,\n ): void => {\n const execute: Record<string, unknown> = controller.execute;\n\n for (const func of controller.application.functions) {\n const toolName: string = prefix\n ? `${controller.name}_${func.name}`\n : func.name;\n\n const method: unknown = execute[func.name];\n if (typeof method !== \"function\") {\n throw new Error(\n `Method \"${func.name}\" not found on controller \"${controller.name}\"`,\n );\n }\n\n tools.push(\n createTool({\n name: toolName,\n function: func,\n execute: async (args: unknown) => method.call(execute, args),\n }),\n );\n }\n };\n\n const convertHttpController = (\n tools: DynamicStructuredTool[],\n controller: IHttpLlmController,\n prefix: boolean,\n ): void => {\n const application = controller.application;\n const connection = controller.connection;\n\n for (const func of application.functions) {\n const toolName: string = prefix\n ? `${controller.name}_${func.name}`\n : func.name;\n\n tools.push(\n createTool({\n name: toolName,\n function: func,\n execute: async (args: unknown) => {\n if (controller.execute !== undefined) {\n const response = await controller.execute({\n connection,\n application,\n function: func,\n arguments: args as object,\n });\n return response.body;\n }\n return HttpLlm.execute({\n application,\n function: func,\n connection,\n input: args as object,\n });\n },\n }),\n );\n }\n };\n\n const createTool = (entry: {\n name: string;\n function: ILlmFunction | IHttpLlmFunction;\n execute: (args: unknown) => Promise<unknown>;\n }): DynamicStructuredTool =>\n tool(\n async (args: unknown): Promise<unknown> => {\n const valid: IValidation<unknown> = LlmJson.validateArguments(\n entry.function,\n args ?? {},\n );\n if (valid.success === false)\n throw new ToolInputParsingException(\n `Type errors in \"${entry.name}\" arguments:\\n\\n` +\n `\\`\\`\\`json\\n${LlmJson.stringify(valid)}\\n\\`\\`\\``,\n JSON.stringify(args ?? {}),\n );\n try {\n const result: unknown = await entry.execute(valid.data);\n if (result === undefined) {\n return entry.function.output === undefined\n ? ({ success: true } satisfies ITryResult)\n : ({\n success: false,\n error: `Function \"${entry.name}\" returned undefined despite declaring an output schema`,\n } satisfies ITryResult);\n }\n return { success: true, data: result } satisfies ITryResult;\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error),\n } satisfies ITryResult;\n }\n },\n {\n name: entry.name,\n description: entry.function.description ?? \"\",\n schema: entry.function.parameters,\n },\n );\n}\n\ntype ITryResult =\n | { success: true; data?: unknown | undefined }\n | { success: false; error: string };\n"],"mappings":";;;AAcO,IAAA;;qCACmB,UAGO;EAC7B,MAAM,SAAkB,MAAM,UAAU;EACxC,MAAM,QAAiC,CAAC;EAGxC,IAAI,WAAW,SAAS,MAAM,YAAY,UAAU,GAAG;GACrD,MAAM,wBAA6B,IAAI,IAAI;GAC3C,MAAM,aAAuB,CAAC;GAC9B,KAAK,MAAM,cAAc,MAAM,aAC7B,KAAK,MAAM,QAAQ,WAAW,YAAY,WAAW;IACnD,MAAM,WAA+B,MAAM,IAAI,KAAK,IAAI;IACxD,IAAI,aAAa,KAAA,GACf,WAAW,KACT,IAAI,KAAK,KAAK,QAAQ,WAAW,KAAK,qBAAqB,SAAS,GACtE;SACG,MAAM,IAAI,KAAK,MAAM,WAAW,IAAI;GAC3C;GAEF,IAAI,WAAW,SAAS,GACtB,MAAM,IAAI,MACR,oCAAoC,WAAW,KAAK,QAAQ,GAC9D;EACJ;EAGA,KAAK,MAAM,cAAc,MAAM,aAC7B,IAAI,WAAW,aAAa,SAC1B,uBAAuB,OAAO,YAAY,MAAM;OAEhD,sBAAsB,OAAO,YAAY,MAAM;EAInD,OAAO;CACT;CAEA,MAAM,0BACJ,OACA,YACA,WACS;EACT,MAAM,UAAmC,WAAW;EAEpD,KAAK,MAAM,QAAQ,WAAW,YAAY,WAAW;GACnD,MAAM,WAAmB,SACrB,GAAG,WAAW,KAAK,GAAG,KAAK,SAC3B,KAAK;GAET,MAAM,SAAkB,QAAQ,KAAK;GACrC,IAAI,OAAO,WAAW,YACpB,MAAM,IAAI,MACR,WAAW,KAAK,KAAK,6BAA6B,WAAW,KAAK,EACpE;GAGF,MAAM,KACJ,WAAW;IACT,MAAM;IACN,UAAU;IACV,SAAS,OAAO,SAAkB,OAAO,KAAK,SAAS,IAAI;GAC7D,CAAC,CACH;EACF;CACF;CAEA,MAAM,yBACJ,OACA,YACA,WACS;EACT,MAAM,cAAc,WAAW;EAC/B,MAAM,aAAa,WAAW;EAE9B,KAAK,MAAM,QAAQ,YAAY,WAAW;GACxC,MAAM,WAAmB,SACrB,GAAG,WAAW,KAAK,GAAG,KAAK,SAC3B,KAAK;GAET,MAAM,KACJ,WAAW;IACT,MAAM;IACN,UAAU;IACV,SAAS,OAAO,SAAkB;KAChC,IAAI,WAAW,YAAY,KAAA,GAOzB,QAAO,MANgB,WAAW,QAAQ;MACxC;MACA;MACA,UAAU;MACV,WAAW;KACb,CAAC,EAAA,CACe;KAElB,OAAO,QAAQ,QAAQ;MACrB;MACA,UAAU;MACV;MACA,OAAO;KACT,CAAC;IACH;GACF,CAAC,CACH;EACF;CACF;CAEA,MAAM,cAAc,UAKlB,KACE,OAAO,SAAoC;EACzC,MAAM,QAA8B,QAAQ,kBAC1C,MAAM,UACN,QAAQ,CAAC,CACX;EACA,IAAI,MAAM,YAAY,OACpB,MAAM,IAAI,0BACR,mBAAmB,MAAM,KAAK,8BACb,QAAQ,UAAU,KAAK,EAAE,WAC1C,KAAK,UAAU,QAAQ,CAAC,CAAC,CAC3B;EACF,IAAI;GACF,MAAM,SAAkB,MAAM,MAAM,QAAQ,MAAM,IAAI;GACtD,IAAI,WAAW,KAAA,GACb,OAAO,MAAM,SAAS,WAAW,KAAA,IAC5B,EAAE,SAAS,KAAK,IAChB;IACC,SAAS;IACT,OAAO,aAAa,MAAM,KAAK;GACjC;GAEN,OAAO;IAAE,SAAS;IAAM,MAAM;GAAO;EACvC,SAAS,OAAO;GACd,OAAO;IACL,SAAS;IACT,OACE,iBAAiB,QACb,GAAG,MAAM,KAAK,IAAI,MAAM,YACxB,OAAO,KAAK;GACpB;EACF;CACF,GACA;EACE,MAAM,MAAM;EACZ,aAAa,MAAM,SAAS,eAAe;EAC3C,QAAQ,MAAM,SAAS;CACzB,CACF;GACH,4BAAA,0BAAA,CAAA,EAAD"}
|
|
1
|
+
{"version":3,"file":"LangChainToolsRegistrar.mjs","names":[],"sources":["../../src/internal/LangChainToolsRegistrar.ts"],"sourcesContent":["import {\n DynamicStructuredTool,\n ToolInputParsingException,\n tool,\n} from \"@langchain/core/tools\";\nimport type { JSONSchema } from \"@langchain/core/utils/json_schema\";\nimport {\n IHttpLlmController,\n IHttpLlmFunction,\n ILlmController,\n ILlmFunction,\n ILlmSchema,\n IValidation,\n} from \"@typia/interface\";\nimport { HttpLlm, LlmJson } from \"@typia/utils\";\n\nimport { LangChainParameterConverter } from \"./LangChainParameterConverter\";\n\nexport namespace LangChainToolsRegistrar {\n export const convert = (props: {\n controllers: Array<ILlmController | IHttpLlmController>;\n prefix?: boolean | undefined;\n }): DynamicStructuredTool[] => {\n const prefix: boolean = props.prefix ?? false;\n const tools: DynamicStructuredTool[] = [];\n\n // check duplicate tool names\n if (props.controllers.length >= 2) {\n const names: Map<string, string> = new Map();\n const duplicates: string[] = [];\n for (const controller of props.controllers) {\n for (const func of controller.application.functions) {\n const toolName: string = getToolName(\n controller.name,\n func.name,\n prefix,\n );\n const existing: string | undefined = names.get(toolName);\n const origin: string = `${controller.protocol} controller \"${controller.name}\" function \"${func.name}\"`;\n if (existing !== undefined)\n duplicates.push(\n `\"${toolName}\" from ${origin} (conflicts with ${existing})`,\n );\n else names.set(toolName, origin);\n }\n }\n if (duplicates.length > 0)\n throw new Error(\n `Duplicate tool names found:\\n - ${duplicates.join(\"\\n - \")}`,\n );\n }\n\n // convert controllers to tools\n for (const controller of props.controllers) {\n if (controller.protocol === \"class\") {\n convertClassController(tools, controller, prefix);\n } else {\n convertHttpController(tools, controller, prefix);\n }\n }\n\n return tools;\n };\n\n const convertClassController = (\n tools: DynamicStructuredTool[],\n controller: ILlmController,\n prefix: boolean,\n ): void => {\n const execute: Record<string, unknown> = controller.execute;\n\n for (const func of controller.application.functions) {\n const toolName: string = getToolName(controller.name, func.name, prefix);\n\n const method: unknown = execute[func.name];\n if (typeof method !== \"function\") {\n throw new Error(\n `Method \"${func.name}\" not found on controller \"${controller.name}\"`,\n );\n }\n\n tools.push(\n createTool({\n name: toolName,\n function: func,\n config: controller.application.config,\n execute: async (args: unknown) => method.call(execute, args),\n }),\n );\n }\n };\n\n const convertHttpController = (\n tools: DynamicStructuredTool[],\n controller: IHttpLlmController,\n prefix: boolean,\n ): void => {\n const application = controller.application;\n const connection = controller.connection;\n\n for (const func of application.functions) {\n const toolName: string = getToolName(controller.name, func.name, prefix);\n\n tools.push(\n createTool({\n name: toolName,\n function: func,\n config: application.config,\n execute: async (args: unknown) => {\n if (controller.execute !== undefined) {\n const response = await controller.execute({\n connection,\n application,\n function: func,\n arguments: args as object,\n });\n return response.body;\n }\n return HttpLlm.execute({\n application,\n function: func,\n connection,\n input: args as object,\n });\n },\n }),\n );\n }\n };\n\n const createTool = (entry: {\n name: string;\n function: ILlmFunction | IHttpLlmFunction;\n config: ILlmSchema.IConfig;\n execute: (args: unknown) => Promise<unknown>;\n }): DynamicStructuredTool => {\n // A declared return type is a promise to the model, so a result that breaks\n // it is reported rather than delivered. `@typia/mcp` and `@typia/vercel`\n // already refuse one; this tool consulted `output` for its existence alone,\n // so a method returning nothing was reported while a method returning the\n // wrong shape was not (#2302).\n //\n // The application's own config decides how the schema is read back: a\n // strict application carries its constraints as description tags instead of\n // keywords, and only that config tells the inverter to read them.\n const validateOutput:\n | ((output: unknown) => IValidation<unknown>)\n | undefined =\n entry.function.output === undefined\n ? undefined\n : LlmJson.validate(entry.function.output, true, entry.config);\n return tool(\n async (args: unknown): Promise<unknown> => {\n const valid: IValidation<unknown> = LlmJson.validateArguments(\n entry.function,\n args ?? {},\n );\n if (valid.success === false)\n throw new ToolInputParsingException(\n `Type errors in \"${entry.name}\" arguments:\\n\\n` +\n LlmJson.stringify(valid),\n JSON.stringify(args ?? {}),\n );\n try {\n const result: unknown = await entry.execute(valid.data);\n if (result === undefined) {\n return validateOutput === undefined\n ? ({ success: true } satisfies ITryResult)\n : ({\n success: false,\n error: `Function \"${entry.name}\" returned undefined despite declaring an output schema`,\n } satisfies ITryResult);\n }\n if (validateOutput !== undefined) {\n const output: IValidation<unknown> = validateOutput(result);\n if (output.success === false)\n return {\n success: false,\n error:\n `Type errors in \"${entry.name}\" output:\n\n` + LlmJson.stringify(output),\n } satisfies ITryResult;\n return { success: true, data: output.data } satisfies ITryResult;\n }\n return { success: true, data: result } satisfies ITryResult;\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error),\n } satisfies ITryResult;\n }\n },\n {\n name: entry.name,\n description: entry.function.description ?? \"\",\n // Declares the model-facing schema without a validator, so the\n // coerce-and-validate step above is the only one that runs; see\n // `LangChainParameterConverter`. The cast is needed because `tool()`\n // still types `schema` as Zod-or-JSON-Schema, even though the\n // `toJsonSchema` it reads that schema back with accepts Standard JSON\n // Schema by its own public signature.\n schema: LangChainParameterConverter.convert(\n entry.function,\n ) as JSONSchema,\n },\n );\n };\n\n const getToolName = (\n controllerName: string,\n functionName: string,\n prefix: boolean,\n ): string => (prefix ? `${controllerName}_${functionName}` : functionName);\n}\n\ntype ITryResult =\n | { success: true; data?: unknown | undefined }\n | { success: false; error: string };\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typia/langchain",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.2.0",
|
|
4
4
|
"description": "LangChain.js integration for typia",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./lib/index.d.mts",
|
|
10
|
+
"default": "./lib/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./lib/index.d.cts",
|
|
14
|
+
"default": "./lib/index.js"
|
|
15
|
+
},
|
|
8
16
|
"types": "./lib/index.d.ts",
|
|
9
|
-
"import": "./lib/index.mjs",
|
|
10
17
|
"default": "./lib/index.js"
|
|
11
18
|
},
|
|
12
19
|
"./package.json": "./package.json"
|
|
@@ -22,18 +29,19 @@
|
|
|
22
29
|
},
|
|
23
30
|
"homepage": "https://typia.io",
|
|
24
31
|
"dependencies": {
|
|
25
|
-
"@
|
|
26
|
-
"@typia/interface": "^13.
|
|
32
|
+
"@standard-schema/spec": "^1.1.0",
|
|
33
|
+
"@typia/interface": "^13.2.0",
|
|
34
|
+
"@typia/utils": "^13.2.0"
|
|
27
35
|
},
|
|
28
36
|
"peerDependencies": {
|
|
29
|
-
"@langchain/core": ">=1.
|
|
37
|
+
"@langchain/core": ">=1.1.30"
|
|
30
38
|
},
|
|
31
39
|
"devDependencies": {
|
|
32
40
|
"@langchain/core": "^1.1.31",
|
|
33
41
|
"rimraf": "^6.1.2",
|
|
34
42
|
"rolldown": "^1.1.5",
|
|
35
43
|
"tinyglobby": "^0.2.12",
|
|
36
|
-
"ttsc": "^0.
|
|
44
|
+
"ttsc": "^0.19.2",
|
|
37
45
|
"typescript": "^7.0.2"
|
|
38
46
|
},
|
|
39
47
|
"sideEffects": false,
|
|
@@ -60,7 +68,7 @@
|
|
|
60
68
|
"access": "public"
|
|
61
69
|
},
|
|
62
70
|
"scripts": {
|
|
63
|
-
"build": "rimraf lib && ttsc && rolldown -c",
|
|
71
|
+
"build": "rimraf lib && ttsc && node ../../scripts/create-conditional-declarations.cjs lib/index.d.ts && rolldown -c",
|
|
64
72
|
"dev": "ttsc --watch"
|
|
65
73
|
},
|
|
66
74
|
"module": "lib/index.mjs",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { IHttpLlmFunction, ILlmFunction } from "@typia/interface";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A Standard JSON Schema view of one typia function's parameters.
|
|
6
|
+
*
|
|
7
|
+
* LangChain's tool `schema` fills two roles at once:
|
|
8
|
+
* `toJsonSchema(tool.schema)` turns it into the parameters the model is shown,
|
|
9
|
+
* and `StructuredTool.call` validates incoming arguments against it before the
|
|
10
|
+
* tool body runs. Handing over a bare `ILlmSchema.IParameters` fills the first
|
|
11
|
+
* role but forfeits the second — LangChain validates it with
|
|
12
|
+
* `@cfworker/json-schema`, which rejects what typia would have coerced and
|
|
13
|
+
* reports the failure in its own words, so typia's coercion and its correctable
|
|
14
|
+
* feedback both become unreachable.
|
|
15
|
+
*
|
|
16
|
+
* Standard JSON Schema separates the two roles. It carries a model-facing
|
|
17
|
+
* schema under `~standard.jsonSchema` and, by declaring no
|
|
18
|
+
* `~standard.validate`, no validator — which is exactly typia's position: show
|
|
19
|
+
* the model this schema, and leave validation to `LlmJson.validateArguments` in
|
|
20
|
+
* the tool body. LangChain reads the schema back through its own
|
|
21
|
+
* `toJsonSchema`, so the model sees the same parameters document as before,
|
|
22
|
+
* byte for byte.
|
|
23
|
+
*
|
|
24
|
+
* `@typia/vercel` states the same contract to the AI SDK through
|
|
25
|
+
* `jsonSchema()`, whose `validate` is likewise left undefined so that
|
|
26
|
+
* `safeValidateTypes` short-circuits its own validation.
|
|
27
|
+
*
|
|
28
|
+
* Two version floors hold this together, and both are pinned in the package
|
|
29
|
+
* manifest. `toJsonSchema` only reads `~standard.jsonSchema` from
|
|
30
|
+
* `@langchain/core@1.1.30` onwards — older versions return the wrapper itself
|
|
31
|
+
* and would advertise _that_ to the model in place of the parameters — and
|
|
32
|
+
* `StandardJSONSchemaV1` only exists from `@standard-schema/spec@1.1.0`.
|
|
33
|
+
*
|
|
34
|
+
* @see https://github.com/standard-schema/standard-schema
|
|
35
|
+
*/
|
|
36
|
+
export namespace LangChainParameterConverter {
|
|
37
|
+
export const convert = (
|
|
38
|
+
func: ILlmFunction | IHttpLlmFunction,
|
|
39
|
+
): StandardJSONSchemaV1 => {
|
|
40
|
+
// Coercion never changes the shape a value must end up in, so the input and
|
|
41
|
+
// output schemas are the same document.
|
|
42
|
+
const jsonSchema = (): Record<string, unknown> =>
|
|
43
|
+
func.parameters as unknown as Record<string, unknown>;
|
|
44
|
+
return {
|
|
45
|
+
"~standard": {
|
|
46
|
+
version: 1,
|
|
47
|
+
vendor: "typia",
|
|
48
|
+
jsonSchema: {
|
|
49
|
+
input: jsonSchema,
|
|
50
|
+
output: jsonSchema,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -3,15 +3,19 @@ import {
|
|
|
3
3
|
ToolInputParsingException,
|
|
4
4
|
tool,
|
|
5
5
|
} from "@langchain/core/tools";
|
|
6
|
+
import type { JSONSchema } from "@langchain/core/utils/json_schema";
|
|
6
7
|
import {
|
|
7
8
|
IHttpLlmController,
|
|
8
9
|
IHttpLlmFunction,
|
|
9
10
|
ILlmController,
|
|
10
11
|
ILlmFunction,
|
|
12
|
+
ILlmSchema,
|
|
11
13
|
IValidation,
|
|
12
14
|
} from "@typia/interface";
|
|
13
15
|
import { HttpLlm, LlmJson } from "@typia/utils";
|
|
14
16
|
|
|
17
|
+
import { LangChainParameterConverter } from "./LangChainParameterConverter";
|
|
18
|
+
|
|
15
19
|
export namespace LangChainToolsRegistrar {
|
|
16
20
|
export const convert = (props: {
|
|
17
21
|
controllers: Array<ILlmController | IHttpLlmController>;
|
|
@@ -21,17 +25,23 @@ export namespace LangChainToolsRegistrar {
|
|
|
21
25
|
const tools: DynamicStructuredTool[] = [];
|
|
22
26
|
|
|
23
27
|
// check duplicate tool names
|
|
24
|
-
if (
|
|
28
|
+
if (props.controllers.length >= 2) {
|
|
25
29
|
const names: Map<string, string> = new Map();
|
|
26
30
|
const duplicates: string[] = [];
|
|
27
31
|
for (const controller of props.controllers) {
|
|
28
32
|
for (const func of controller.application.functions) {
|
|
29
|
-
const
|
|
33
|
+
const toolName: string = getToolName(
|
|
34
|
+
controller.name,
|
|
35
|
+
func.name,
|
|
36
|
+
prefix,
|
|
37
|
+
);
|
|
38
|
+
const existing: string | undefined = names.get(toolName);
|
|
39
|
+
const origin: string = `${controller.protocol} controller "${controller.name}" function "${func.name}"`;
|
|
30
40
|
if (existing !== undefined)
|
|
31
41
|
duplicates.push(
|
|
32
|
-
`"${
|
|
42
|
+
`"${toolName}" from ${origin} (conflicts with ${existing})`,
|
|
33
43
|
);
|
|
34
|
-
else names.set(
|
|
44
|
+
else names.set(toolName, origin);
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
if (duplicates.length > 0)
|
|
@@ -60,9 +70,7 @@ export namespace LangChainToolsRegistrar {
|
|
|
60
70
|
const execute: Record<string, unknown> = controller.execute;
|
|
61
71
|
|
|
62
72
|
for (const func of controller.application.functions) {
|
|
63
|
-
const toolName: string = prefix
|
|
64
|
-
? `${controller.name}_${func.name}`
|
|
65
|
-
: func.name;
|
|
73
|
+
const toolName: string = getToolName(controller.name, func.name, prefix);
|
|
66
74
|
|
|
67
75
|
const method: unknown = execute[func.name];
|
|
68
76
|
if (typeof method !== "function") {
|
|
@@ -75,6 +83,7 @@ export namespace LangChainToolsRegistrar {
|
|
|
75
83
|
createTool({
|
|
76
84
|
name: toolName,
|
|
77
85
|
function: func,
|
|
86
|
+
config: controller.application.config,
|
|
78
87
|
execute: async (args: unknown) => method.call(execute, args),
|
|
79
88
|
}),
|
|
80
89
|
);
|
|
@@ -90,14 +99,13 @@ export namespace LangChainToolsRegistrar {
|
|
|
90
99
|
const connection = controller.connection;
|
|
91
100
|
|
|
92
101
|
for (const func of application.functions) {
|
|
93
|
-
const toolName: string = prefix
|
|
94
|
-
? `${controller.name}_${func.name}`
|
|
95
|
-
: func.name;
|
|
102
|
+
const toolName: string = getToolName(controller.name, func.name, prefix);
|
|
96
103
|
|
|
97
104
|
tools.push(
|
|
98
105
|
createTool({
|
|
99
106
|
name: toolName,
|
|
100
107
|
function: func,
|
|
108
|
+
config: application.config,
|
|
101
109
|
execute: async (args: unknown) => {
|
|
102
110
|
if (controller.execute !== undefined) {
|
|
103
111
|
const response = await controller.execute({
|
|
@@ -123,9 +131,25 @@ export namespace LangChainToolsRegistrar {
|
|
|
123
131
|
const createTool = (entry: {
|
|
124
132
|
name: string;
|
|
125
133
|
function: ILlmFunction | IHttpLlmFunction;
|
|
134
|
+
config: ILlmSchema.IConfig;
|
|
126
135
|
execute: (args: unknown) => Promise<unknown>;
|
|
127
|
-
}): DynamicStructuredTool =>
|
|
128
|
-
|
|
136
|
+
}): DynamicStructuredTool => {
|
|
137
|
+
// A declared return type is a promise to the model, so a result that breaks
|
|
138
|
+
// it is reported rather than delivered. `@typia/mcp` and `@typia/vercel`
|
|
139
|
+
// already refuse one; this tool consulted `output` for its existence alone,
|
|
140
|
+
// so a method returning nothing was reported while a method returning the
|
|
141
|
+
// wrong shape was not (#2302).
|
|
142
|
+
//
|
|
143
|
+
// The application's own config decides how the schema is read back: a
|
|
144
|
+
// strict application carries its constraints as description tags instead of
|
|
145
|
+
// keywords, and only that config tells the inverter to read them.
|
|
146
|
+
const validateOutput:
|
|
147
|
+
| ((output: unknown) => IValidation<unknown>)
|
|
148
|
+
| undefined =
|
|
149
|
+
entry.function.output === undefined
|
|
150
|
+
? undefined
|
|
151
|
+
: LlmJson.validate(entry.function.output, true, entry.config);
|
|
152
|
+
return tool(
|
|
129
153
|
async (args: unknown): Promise<unknown> => {
|
|
130
154
|
const valid: IValidation<unknown> = LlmJson.validateArguments(
|
|
131
155
|
entry.function,
|
|
@@ -134,19 +158,31 @@ export namespace LangChainToolsRegistrar {
|
|
|
134
158
|
if (valid.success === false)
|
|
135
159
|
throw new ToolInputParsingException(
|
|
136
160
|
`Type errors in "${entry.name}" arguments:\n\n` +
|
|
137
|
-
|
|
161
|
+
LlmJson.stringify(valid),
|
|
138
162
|
JSON.stringify(args ?? {}),
|
|
139
163
|
);
|
|
140
164
|
try {
|
|
141
165
|
const result: unknown = await entry.execute(valid.data);
|
|
142
166
|
if (result === undefined) {
|
|
143
|
-
return
|
|
167
|
+
return validateOutput === undefined
|
|
144
168
|
? ({ success: true } satisfies ITryResult)
|
|
145
169
|
: ({
|
|
146
170
|
success: false,
|
|
147
171
|
error: `Function "${entry.name}" returned undefined despite declaring an output schema`,
|
|
148
172
|
} satisfies ITryResult);
|
|
149
173
|
}
|
|
174
|
+
if (validateOutput !== undefined) {
|
|
175
|
+
const output: IValidation<unknown> = validateOutput(result);
|
|
176
|
+
if (output.success === false)
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error:
|
|
180
|
+
`Type errors in "${entry.name}" output:
|
|
181
|
+
|
|
182
|
+
` + LlmJson.stringify(output),
|
|
183
|
+
} satisfies ITryResult;
|
|
184
|
+
return { success: true, data: output.data } satisfies ITryResult;
|
|
185
|
+
}
|
|
150
186
|
return { success: true, data: result } satisfies ITryResult;
|
|
151
187
|
} catch (error) {
|
|
152
188
|
return {
|
|
@@ -161,9 +197,24 @@ export namespace LangChainToolsRegistrar {
|
|
|
161
197
|
{
|
|
162
198
|
name: entry.name,
|
|
163
199
|
description: entry.function.description ?? "",
|
|
164
|
-
schema
|
|
200
|
+
// Declares the model-facing schema without a validator, so the
|
|
201
|
+
// coerce-and-validate step above is the only one that runs; see
|
|
202
|
+
// `LangChainParameterConverter`. The cast is needed because `tool()`
|
|
203
|
+
// still types `schema` as Zod-or-JSON-Schema, even though the
|
|
204
|
+
// `toJsonSchema` it reads that schema back with accepts Standard JSON
|
|
205
|
+
// Schema by its own public signature.
|
|
206
|
+
schema: LangChainParameterConverter.convert(
|
|
207
|
+
entry.function,
|
|
208
|
+
) as JSONSchema,
|
|
165
209
|
},
|
|
166
210
|
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const getToolName = (
|
|
214
|
+
controllerName: string,
|
|
215
|
+
functionName: string,
|
|
216
|
+
prefix: boolean,
|
|
217
|
+
): string => (prefix ? `${controllerName}_${functionName}` : functionName);
|
|
167
218
|
}
|
|
168
219
|
|
|
169
220
|
type ITryResult =
|