Skip to Content

toLangChainTools() function

undefined

@typia/langchain
export function toLangChainTools(props: { controllers: Array<ILlmController | IHttpLlmController>; prefix?: boolean | undefined; }): DynamicStructuredTool[];

LangChain.js  integration for typia.

toLangChainTools() converts TypeScript classes or OpenAPI documents into LangChain DynamicStructuredTool[] at once.

Every class method becomes a tool, JSDoc comments become tool descriptions, and TypeScript types become JSON schemas — all at compile time. For OpenAPI documents, every API endpoint is converted to a DynamicStructuredTool with schemas from the specification.

Lenient JSON parsing, type coercion, and validation feedback are all embedded automatically — the complete function calling harness that turns unreliable LLM output into 100% correct structured data.

Setup

Terminal
npm install @typia/langchain @langchain/core npm install typia npx typia setup

From TypeScript Class

src/main.ts
import { ChainValues, Runnable } from "@langchain/core"; import { ChatPromptTemplate } from "@langchain/core/prompts"; import { DynamicStructuredTool } from "@langchain/core/tools"; import { ChatOpenAI } from "@langchain/openai"; import { toLangChainTools } from "@typia/langchain"; import { AgentExecutor, createToolCallingAgent } from "langchain/agents"; import typia from "typia"; import { Calculator } from "./Calculator"; const tools: DynamicStructuredTool[] = toLangChainTools({ controllers: [ typia.llm.controller<Calculator>("calculator", new Calculator()), ], }); const agent: Runnable = createToolCallingAgent({ llm: new ChatOpenAI({ model: "gpt-4o" }), tools, prompt: ChatPromptTemplate.fromMessages([ ["system", "You are a helpful assistant."], ["human", "{input}"], ["placeholder", "{agent_scratchpad}"], ]), }); const executor: AgentExecutor = new AgentExecutor({ agent, tools }); const result: ChainValues = await executor.invoke({ input: "What is 10 + 5?", });

Create controllers from TypeScript classes with typia.llm.controller<Class>(), and pass them to toLangChainTools().

  • controllers: Array of controllers created via typia.llm.controller<Class>() or HttpLlm.controller()
  • prefix: When true (default), tool names are formatted as {controllerName}_{methodName}. Set to false to use bare method names

Type Restrictions

Every method’s parameter type must be a keyworded object type with static keys — not a primitive, array, or union. The return type must also be an object type or void. Primitive return types like number or string are not allowed; wrap them in an object (e.g., { value: number }). See typia.llm.application() Restrictions for details.

From OpenAPI Document

src/main.ts
import { DynamicStructuredTool } from "@langchain/core/tools"; import { toLangChainTools } from "@typia/langchain"; import { HttpLlm } from "@typia/utils"; const tools: DynamicStructuredTool[] = toLangChainTools({ controllers: [ HttpLlm.controller({ name: "shopping", document: await fetch( "https://shopping-be.wrtn.ai/editor/swagger.json", ).then((r) => r.json()), connection: { host: "https://shopping-be.wrtn.ai", headers: { Authorization: "Bearer ********" }, }, }), ], });

Create controllers from OpenAPI documents with HttpLlm.controller(), and pass them to toLangChainTools().

  • name: Controller name used as prefix for tool names
  • document: Swagger/OpenAPI document (v2.0, v3.0, or v3.1)
  • connection: HTTP connection info including host and optional headers

The Function Calling Harness

toLangChainTools() embeds lenient JSON parsing, type coercion, and validation feedback in every tool — all automatically. When validation fails, the error is returned as text content with inline // ❌ comments at each invalid property:

{ "name": "John", "age": "twenty", // ❌ [{"path":"$input.age","expected":"number"}] "email": "not-an-email", // ❌ [{"path":"$input.email","expected":"string & Format<\"email\">"}] "hobbies": "reading" // ❌ [{"path":"$input.hobbies","expected":"Array<string>"}] }

The LLM reads this feedback and self-corrects on the next turn.

Bypassing LangChain’s Built-in Validation

LangChain internally uses @cfworker/json-schema to validate tool arguments, which throws ToolInputParsingException before custom validation can run. @typia/langchain solves this by using a passthrough Zod schema (z.record(z.unknown())), allowing typia’s much more detailed and accurate validator to handle all argument validation instead.

In the AutoBe  project (AI-powered backend code generator by Wrtn Technologies ), qwen3-coder-next showed only 6.75% raw function calling success rate on compiler AST types. However, with the complete harness, it reached 100% — across all four tested Qwen models.

Working on compiler AST means working on any type and any use case.

AutoBeTest.IExpression
// Compiler AST may be the hardest type structure possible // // Unlimited union types + unlimited depth + recursive references export type IExpression = | IBooleanLiteral | INumericLiteral | IStringLiteral | IArrayLiteralExpression // <- recursive (contains IExpression[]) | IObjectLiteralExpression // <- recursive (contains IExpression) | INullLiteral | IUndefinedKeyword | IIdentifier | IPropertyAccessExpression // <- recursive | IElementAccessExpression // <- recursive | ITypeOfExpression // <- recursive | IPrefixUnaryExpression // <- recursive | IPostfixUnaryExpression // <- recursive | IBinaryExpression // <- recursive (left & right) | IArrowFunction // <- recursive (body is IExpression) | ICallExpression // <- recursive (args are IExpression[]) | INewExpression // <- recursive | IConditionalPredicate // <- recursive (then & else branches) | ... // 30+ expression types total

Structured Output

Use typia.llm.parameters<T>() with LangChain’s withStructuredOutput():

src/main.ts
import { ChatOpenAI } from "@langchain/openai"; import { dedent, LlmJson } from "@typia/utils"; import typia, { tags } from "typia"; interface IMember { email: string & tags.Format<"email">; name: string; age: number & tags.Minimum<0> & tags.Maximum<100>; hobbies: string[]; joined_at: string & tags.Format<"date">; } const model = new ChatOpenAI({ model: "gpt-4o" }) .withStructuredOutput(typia.llm.parameters<IMember>()); const member: IMember = await model.invoke(dedent` I am a new member of the community. My name is John Doe, and I am 25 years old. I like playing basketball and reading books, and joined to this community at 2022-01-01. `); // Validate the result const result = typia.validate<IMember>(member); if (!result.success) { console.error(LlmJson.stringify(result)); }
Terminal
{ email: 'john.doe@example.com', name: 'John Doe', age: 25, hobbies: [ 'playing basketball', 'reading books' ], joined_at: '2022-01-01' }

The IMember interface is the single source of truth. typia.llm.parameters<IMember>() generates the JSON schema, and typia.validate<IMember>() validates the output — all from the same type. If validation fails, feed the error back to the LLM for correction.

Last updated on