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