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 (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.

HTTP request and response contracts

Path parameters, headers, cookies, query parameters, and request bodies remain separate argument groups in each generated function. OpenAPI required, style, and explode metadata controls both the LLM schema and the executed request, so optional query or body groups may be omitted and arrays or objects use their declared wire format.

Ordinary styled query parameters preserve OpenAPI’s structural syntax while encoding names, values, and URI-required delimiters with RFC 3986 rules. An OpenAPI 3.2 querystring parameter instead represents the complete query as one content-backed value; for example, application/x-www-form-urlencoded uses form encoding, including + for spaces.

Connection headers provide defaults, operation arguments override matching headers, and a serialized body selects its final Content-Type. Cookie arguments are merged by cookie name with any connection-level Cookie header. The default form cookie style percent-encodes names and values, while OpenAPI 3.2’s cookie style passes pre-escaped data through unchanged. Browsers may still prevent JavaScript from setting restricted headers such as Cookie.

Every 2xx response is treated as successful, including bodyless 204 and 205 responses. Failed responses retain their parsed JSON, form, multipart, binary, or text body in HttpError, and repeated Set-Cookie fields are exposed as separate response-header values when the runtime provides that information.

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 (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