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
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
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 callEach API operation becomes an IHttpLlmFunction carrying:
- JSON Schema for parameters
- The OpenAPI description as the toolβs
description - The same
parse,coerce,validatemethods you get fromtypia.llm.applicationβ same harness, same LLM auto-correction story
undefined
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
| Argument | Meaning |
|---|---|
name | Controller name. Becomes the prefix for tool names ({name}_{operationId}) |
document | The OpenAPI document β Swagger 2.0 / OpenAPI 3.0 / 3.1 / 3.2 are all accepted |
connection.host | Base URL for HTTP requests |
connection.headers | Optional headers (auth, etc.) sent with every request |
config | Optional schema-conversion tuning |
execute | Optional 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:
Vercel AI SDK
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
Parsing (text β object)
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.
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 totalFor the full mechanics of parse, coerce, validate, and stringify, see LlmJson.
Where to go next
- TypeScript class as the source instead of OpenAPI β
typia.llm.application - Framework adapters β Vercel Β· LangChain
- Harness internals β
LlmJson - Building a chatbot on top β Agentica