Skip to Content
📖 Guide DocumentsLLM Function CallingGuide Documents > Large Language Model > Documentation Strategy

Documentation Strategy

LLMs decide which function to call by reading its description. Good descriptions are not optional — they are the prompt. This page covers three practical patterns that make the LLM behave better without changing any TypeScript types:

  1. Description comments — JSDoc on the method and on the parameter type
  2. Namespace inheritance — description on the parent type cascades into children
  3. Hiding methods — exclude internal helpers from the schema

Plus two carry-overs from the schema layer for reference:

  1. Specialization@title, @deprecated, @hidden, @internal
  2. Custom x-… fields — vendor extensions in JSON Schema

Description comments

The LLM reads:

  • The method JSDoc comment as the function’s description
  • Property JSDoc comments as each property’s description
  • The @title comment tag (if present) as the property’s title
examples/src/llm/BbsArticleController.ts
class BbsArticleController { /** * Create a new article. * * Writes a new article and archives it into the DB. */ async create(props: { /** * Information of the article to create. */ input: IBbsArticle.ICreate; }): Promise<IBbsArticle> { /* … */ } }

Console output of what the LLM sees for create:

Create a new article. Writes a new article and archives it into the DB.

The more the description tells the LLM about when to call the function (not just what it returns), the more reliably it picks the right one.

examples/src/llm/application-description.ts
import typia, { ILlmApplication, ILlmFunction, tags } from "typia"; const app: ILlmApplication = typia.llm.application<BbsArticleController>(); const func: ILlmFunction | undefined = app.functions.find( (func) => func.name === "create", ); console.log(func?.description); interface BbsArticleController { /** * Create a new article. * * Writes a new article and archives it into the DB. * * @param props Properties of create function * @returns Newly created article */ create(props: { /** Information of the article to create */ input: IBbsArticle.ICreate; }): Promise<IBbsArticle>; /** * Update an article. * * Updates an article with new content. * * @param props Properties of update function * @param input New content to update */ update(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; /** New content to update. */ input: IBbsArticle.IUpdate; }): Promise<void>; /** * Erase an article. * * Erases an article from the DB. * * @param props Properties of erase function */ erase(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; }): Promise<void>; } /** * Article entity. * * `IBbsArticle` is an entity representing an article in the BBS (Bulletin Board * System). */ interface IBbsArticle extends IBbsArticle.ICreate { /** Primary Key. */ id: string & tags.Format<"uuid">; /** Creation time of the article. */ created_at: string & tags.Format<"date-time">; /** Last updated time of the article. */ updated_at: string & tags.Format<"date-time">; } namespace IBbsArticle { /** Information of the article to create. */ export interface ICreate { /** * Title of the article. * * Representative title of the article. */ title: string; /** * Content body. * * Content body of the article writtn in the markdown format. */ body: string; /** * Thumbnail image URI. * * Thumbnail image URI which can represent the article. * * If configured as `null`, it means that no thumbnail image in the article. */ thumbnail: | null | (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">); } /** * Information of the article to update. * * Only the filled properties will be updated. */ export type IUpdate = Partial<ICreate>; }

description vs title

ILlmFunction has one description. ILlmSchema has both description and title. Use @title to add a short heading on a property without it being repeated in the description:

examples/src/llm/IMember.ts
import { tags } from "typia"; export interface IMember { /** * Primary Key. * * This description would be the description of LLM schema. */ id: string; /** * Below "Age of the member" would be the title of LLM schema. * * @title Age of the member */ age: number & tags.Type<"uint32"> & tags.ExclusiveMinimum<19> & tags.Maximum<100>; /** * The description property also can be filled by the comment tag * `@description`. Instead, be careful about the indentation. * * @title Email address of the member */ email: string & tags.Format<"email">; }
Note

You can also fill description with the @description comment tag, but watch the indentation — every continuation line of a @description block must keep its leading whitespace consistent, otherwise only the first line gets captured and the rest is silently dropped. If a generated schema’s description looks oddly truncated, this is almost always the cause. Plain JSDoc (no @description tag) doesn’t have this problem.

Namespace inheritance

CRUD-style controllers tend to have parent types (IBbsArticle) and child variants (IBbsArticle.ICreate, IBbsArticle.IUpdate). Repeating the same long description on every variant is tedious. typia copies the parent’s description into the children automatically:

examples/src/llm/application-namespace.ts
import typia, { ILlmApplication, ILlmFunction, tags } from "typia"; const app: ILlmApplication = typia.llm.application<BbsArticleController>(); const func: ILlmFunction | undefined = app.functions.find( (func) => func.name === "create", ); console.log(func?.parameters.properties.input?.description); /** * Article entity. * * `IBbsArticle` is an entity representing an article in the BBS (Bulletin Board * System). */ interface IBbsArticle extends IBbsArticle.ICreate { /** Primary Key. */ id: string & tags.Format<"uuid">; /** Creation time of the article. */ created_at: string & tags.Format<"date-time">; /** Last updated time of the article. */ updated_at: string & tags.Format<"date-time">; } namespace IBbsArticle { /** Information of the article to create. */ export interface ICreate { /** * Title of the article. * * Representative title of the article. */ title: string; /** * Content body. * * Content body of the article writtn in the markdown format. */ body: string; /** * Thumbnail image URI. * * Thumbnail image URI which can represent the article. * * If configured as `null`, it means that no thumbnail image in the article. */ thumbnail: | null | (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">); } /** * Information of the article to update. * * Only the filled properties will be updated. */ export type IUpdate = Partial<ICreate>; } interface BbsArticleController { /** * Create a new article. * * Writes a new article and archives it into the DB. * * @param props Properties of create function * @returns Newly created article */ create(props: { /** Information of the article to create */ input: IBbsArticle.ICreate; }): Promise<IBbsArticle>; /** * Update an article. * * Updates an article with new content. * * @param props Properties of update function * @param input New content to update */ update(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; /** New content to update. */ input: IBbsArticle.IUpdate; }): Promise<void>; /** * Erase an article. * * Erases an article from the DB. * * @param props Properties of erase function */ erase(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; }): Promise<void>; }

Write the parent’s description once, give each child a short variant-specific note, and the LLM sees both — the variant note and the inherited parent context.

Hiding methods

Sometimes you want a method on the class but not on the LLM’s tool list — an internal helper, a sensitive admin action, anything the LLM shouldn’t be calling. Use one of these JSDoc tags on the method:

TagMeans
@hiddenExcluded from the LLM application
@internalExcluded (TypeScript convention for “not part of the public API”)
@humanExcluded (typia convention for “only humans, not LLMs, should call this”)
examples/src/llm/application-hidden.ts
import typia, { ILlmApplication, tags } from "typia"; import { IBbsArticle } from "./IBbsArticle"; const app: ILlmApplication = typia.llm.application<BbsArticleController>(); console.log(app); interface BbsArticleController { /** * Create a new article. * * Writes a new article and archives it into the DB. * * @param props Properties of create function * @returns Newly created article */ create(props: { /** Information of the article to create */ input: IBbsArticle.ICreate; }): Promise<IBbsArticle>; /** * Read an article. * * Reads an article from the DB. * * @ignore * @param props Properties of read function * @returns The article */ at(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; }): Promise<IBbsArticle>; /** * Update an article. * * Updates an article with new content. * * @param props Properties of update function * @param input New content to update * @internal */ update(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; /** New content to update. */ input: IBbsArticle.IUpdate; }): Promise<void>; /** * Erase an article. * * Erases an article from the DB. * * @param props Properties of erase function * @human */ erase(props: { /** Target article's {@link IBbsArticle.id}. */ id: string & tags.Format<"uuid">; }): Promise<void>; }

Hidden methods don’t appear in ILlmApplication.functions, so the LLM never sees them. They still exist on the class instance and you can call them normally from your own code.

Specialization

The same comment tags work for properties on the parameter and return types:

TagEffect
@title SomethingShort title shown alongside description
@deprecatedMarks the property as deprecated in the schema
@hidden / @internalExcludes the property from the emitted schema
examples/src/llm/schema-special.ts
import typia, { ILlmSchema, tags } from "typia"; export const $defs: Record<string, ILlmSchema> = {}; export const schema: ILlmSchema = typia.llm.schema<Special>($defs); interface Special { /** * Deprecated tags are just used for marking. * * @deprecated * @title Unsigned integer */ type: number & tags.Type<"int32">; /** * Internal tagged property never be shown in JSON schema. * * It even doesn't be shown in other `typia` functions like `assert<T>()`. * * @internal */ internal: number[]; /** * Hidden tagged property never be shown in JSON schema. * * However, it would be shown in other `typia` functions like * `stringify<T>()`. * * @ignore */ hidden: boolean; /** * You can limit the range of number. * * @exclusiveMinimum 19 * @maximum 100 */ number?: number; /** * You can limit the length of string. * * Also, multiple range conditions are also possible. */ string: string & ( | (tags.MinLength<3> & tags.MaxLength<24>) | (tags.MinLength<40> & tags.MaxLength<100>) ); /** * You can limit the pattern of string. * * @pattern ^[a-z]+$ */ pattern: string; /** * You can limit the format of string. * * @format date-time */ format: string | null; /** In the Array case, possible to restrict its elements. */ array: Array<string & tags.Format<"uuid">> & tags.MinItems<3>; }

Also see type tags — the same Format, Minimum, MaxLength, etc. that drive runtime validation also flow into the emitted JSON Schema as format, minimum, maxLength.

Customization

JSON Schema reserves the x- prefix for non-standard extensions. To add x-… keys, use tags.TagBase.schema or the tags.JsonSchemaPlugin shortcut:

examples/src/llm/schema-custom.ts
import typia, { ILlmSchema, tags } from "typia"; export const $defs: Record<string, ILlmSchema> = {}; export const schema: ILlmSchema = typia.llm.schema<IAccount>($defs); type Monetary<Value extends string> = tags.TagBase<{ target: "number"; kind: "monetary"; value: Value; schema: { "x-monetary": Value; }; }>; type Placeholder<Value extends string> = tags.JsonSchemaPlugin<{ "x-placeholder": Value; }>; interface IAccount { code: string & Placeholder<"Write you account code please">; balance: number & Monetary<"dollar">; }

Where to go next

Last updated on