Skip to Content

HttpLlm — LLM function calling from an OpenAPI document

typia.llm.application<Class> turns a TypeScript class into LLM tools at compile time. HttpLlm does the same thing — but starting from an OpenAPI (Swagger) document, at runtime.

Use it when:

  • You have an existing REST API (yours or someone else’s) and want an LLM to be able to call it
  • The API is documented with Swagger 2.0 / OpenAPI 3.0 / 3.1 / 3.2
  • You don’t necessarily own the TypeScript code for the server
signature
namespace HttpLlm { function controller(props: { name: string; document: SwaggerV2.IDocument | OpenApiV3.IDocument | OpenApiV3_1.IDocument | OpenApiV3_2.IDocument; connection: IHttpConnection; config?: Partial<IHttpLlmApplication.IConfig>; execute?: IHttpLlmController["execute"]; }): IHttpLlmController; }

HttpLlm lives in @typia/utils. Install it alongside the framework adapter you want to wire it into (MCP, Vercel, LangChain).

First example

hello-httplm.ts
import { HttpLlm } from "@typia/utils"; import { IHttpLlmController } from "@typia/interface"; const controller: IHttpLlmController = 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 ********" }, }, }); // controller.application.functions: one IHttpLlmFunction per API operation // controller.execute(funcName, args): performs the actual HTTP call

Each API operation becomes an IHttpLlmFunction carrying:

  • JSON Schema for parameters
  • The OpenAPI description as the tool’s description
  • The same parse, coerce, validate methods you get from typia.llm.application — same harness, same LLM auto-correction story

undefined

@typia/utils
export namespace HttpLlm { export function controller(props: { name: string; document: | SwaggerV2.IDocument | OpenApiV3.IDocument | OpenApiV3_1.IDocument | OpenApiV3_2.IDocument; connection: IHttpConnection; config?: Partial<IHttpLlmApplication.IConfig>; execute?: IHttpLlmController["execute"]; }): IHttpLlmController; }

What each argument does

ArgumentMeaning
nameController name. Becomes the prefix for tool names ({name}_{operationId})
documentThe OpenAPI document — Swagger 2.0 / OpenAPI 3.0 / 3.1 / 3.2 are all accepted
connection.hostBase URL for HTTP requests
connection.headersOptional headers (auth, etc.) sent with every request
configOptional schema-conversion tuning
executeOptional custom HTTP executor. Defaults to HttpLlm.execute

Conversion pipeline

HttpLlm first normalizes every OpenAPI dialect into one internal representation (emended OpenAPI 3.2), then converts each operation into an IHttpLlmFunction. The intermediate representation is what makes “any version in, any LLM provider out” possible without combinatorial conversion code.

Framework adapters

The same controller plugs into any supported framework. Pick whichever your app already uses:

src/main.ts
import { openai } from "@ai-sdk/openai"; import { toVercelTools } from "@typia/vercel"; import { generateText, Tool } from "ai"; import { HttpLlm } from "@typia/utils"; const tools: Record<string, Tool> = toVercelTools({ 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 ********" }, }, }), ], }); const result = await generateText({ model: openai("gpt-4o"), tools, prompt: "I wanna buy MacBook Pro", });

The function calling harness

Every IHttpLlmFunction carries the same parse / coerce / validate methods as ILlmFunction — same parse → coerce → validate → stringify-feedback story as typia.llm.application. The two pages share machinery; if you’ve already read the application page, the rest of this section is a refresher.

Parse and coerce

examples/src/llm/application-parse.ts
import { dedent } from "@typia/utils"; import typia, { ILlmApplication, ILlmFunction, tags } from "typia"; const app: ILlmApplication = typia.llm.application<OrderService>(); const func: ILlmFunction = app.functions[0]; // LLM sometimes returns malformed JSON with wrong types const llmOutput = dedent` > LLM sometimes returns some prefix text with markdown JSON code block. I'd be happy to help you with your order! 😊 \`\`\`json { "order": { "payment": "{\"type\":\"card\",\"cardNumber\":\"1234-5678", // unclosed string & bracket "product": { name: "Laptop", // unquoted key price: "1299.99", // wrong type (string instead of number) quantity: 2, // trailing comma }, "customer": { // incomplete keyword + unclosed brackets "name": "John Doe", "email": "john@example.com", vip: tru \`\`\` `; const result = func.parse(llmOutput); if (result.success) console.log(result); interface IOrder { payment: IPayment; product: { name: string; price: number & tags.Minimum<0>; quantity: number & tags.Type<"uint32">; }; customer: { name: string; email: string & tags.Format<"email">; vip: boolean; }; } type IPayment = | { type: "card"; cardNumber: string } | { type: "bank"; accountNumber: string }; declare class OrderService { /** * Create a new order. * * @param props Order properties */ createOrder(props: { order: IOrder }): { id: string }; }

Same parse vs coerce rule

  • LLM-emitted text (raw string) → parse(text)
  • Pre-parsed object (from the SDK) → coerce(obj)

Both run the same type-coercion logic; parse adds the lenient JSON layer in front.

Validation feedback

When validation fails inside a framework adapter (MCP / Vercel / LangChain), the tool returns annotated error JSON the LLM can read:

{ "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>"}] }

Same pattern as on application. Same effect — LLMs self-correct on the next turn instead of producing structured nonsense.

In production at AutoBe , the harness took qwen3-coder-next from 6.75% raw correctness to 100% on compiler AST types — across all four tested Qwen models. AST types are the hardest realistic target (unbounded unions × unbounded depth × recursive references), so if the harness handles those, it handles your REST API.

AutoBeTest.IExpression — the kind of type the harness handles
export type IExpression = | IBooleanLiteral | INumericLiteral | IStringLiteral | IArrayLiteralExpression // recursive | IObjectLiteralExpression // recursive | INullLiteral | IUndefinedKeyword | IIdentifier | IPropertyAccessExpression // recursive | IElementAccessExpression // recursive | ITypeOfExpression // recursive | IPrefixUnaryExpression // recursive | IPostfixUnaryExpression // recursive | IBinaryExpression // recursive | IArrowFunction // recursive | ICallExpression // recursive | INewExpression // recursive | IConditionalPredicate // recursive | ... // 30+ expression types total

For the full mechanics of parse, coerce, validate, and stringify, see LlmJson.

Where to go next

Last updated on