Skip to Content
πŸ“– Guide DocumentsLLM Function CallingGuide Documents > Large Language Model > AI Chatbot Development

Building a Chatbot with Agentica

typia.llm.application<Class>() gives you the tools an LLM can call. AgenticaΒ  builds the agent loop around those tools β€” the orchestration layer that decides which function to call, when, and how to recover from mistakes.

If typia.llm.application is β€œdescribe my service to an LLM,” Agentica is β€œactually run a conversation in which an LLM uses that service.”

First example

src/index.ts
import { Agentica } from "@agentica/core"; import OpenAI from "openai"; import typia from "typia"; import { BbsArticleService } from "./BbsArticleService"; const agent = new Agentica({ service: { api: new OpenAI({ apiKey: "*****" }), model: "openai/gpt-4.1-mini", }, controllers: [ typia.llm.controller<BbsArticleService>( "bbs", new BbsArticleService(), ), ], }); await agent.conversate("Hello, I want to create an article.");

That’s a complete chatbot: it understands intent, picks the right method on BbsArticleService, fills the arguments from the conversation, and asks follow-up questions when arguments are missing. No prompt engineering, no agent graph definition.

agentica-conceptual-diagram

@nestia/agent was renamed to @agentica/* to make room for additional packages built on the same idea.

Full source

undefined

src/index.ts
import { Agentica } from "@agentica/core"; import OpenAI from "openai"; import typia from "typia"; import { BbsArticleService } from "./BbsArticleService"; const agent = new Agentica({ service: { api: new OpenAI({ apiKey: "*****" }), model: "openai/gpt-4.1-mini", }, controllers: [ typia.llm.controller<BbsArticleService>( "bbs", new BbsArticleService(), ), ], }); await agent.conversate("Hello, I want to create an article.");

From an OpenAPI document

You don’t have to start from TypeScript code. Agentica accepts an OpenAPI document directly β€” every endpoint becomes a tool the LLM can call.

src/main.ts
import { Agentica, assertHttpController } from "@agentica/core"; import OpenAI from "openai"; import typia from "typia"; import { MobileFileSystem } from "./services/MobileFileSystem"; const agent = new Agentica({ vendor: { api: new OpenAI({ apiKey: "********" }), model: "openai/gpt-4.1-mini", }, controllers: [ // Class-based functions typia.llm.controller<MobileFileSystem>("filesystem", new MobileFileSystem()), // Swagger / OpenAPI document β†’ functions assertHttpController({ 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 ********" }, }, }), ], }); await agent.conversate("I wanna buy MacBook Pro");

How the loop works

Three sub-agents share the work:

  • Selector β€” reads the latest user message, decides which of the registered functions are candidates this turn. If none apply, it falls back to plain conversational replies.
  • Caller β€” given the candidate functions, tries to call them. If the conversation hasn’t given it enough information for the parameters, it asks the user a follow-up.
  • Describer β€” once the caller has produced results, turns them into a human-readable reply.

That separation is what keeps Agentica simple: each sub-agent does one thing and you don’t have to draw a state graph to keep it on track.

Validation feedback

LLM function calling is not perfect. Models make type mistakes β€” for example, they fill an Array<string> field with just a string. The fix is to give the model the structured error and ask it to retry.

Agentica relies on the same harness as typia.llm.application:

import { FunctionCall } from "pseudo"; import { ILlmFunction, IValidation } from "typia"; export const correctFunctionCall = (p: { call: FunctionCall; functions: Array<ILlmFunction<"chatgpt">>; retry: (reason: string, errors?: IValidation.IError[]) => Promise<unknown>; }): Promise<unknown> => { const func = p.functions.find((f) => f.name === p.call.name); if (func === undefined) { return p.retry("Unable to find the matched function name. Try it again."); } const result: IValidation<unknown> = func.validate(p.call.arguments); if (!result.success) { // 1st trial: ~50% pass (gpt-4o-mini on shopping mall demo) // 2nd trial with validation feedback: ~99% // 3rd trial with validation feedback again: never failed in my testing return p.retry( "Type errors are detected. Correct it through validation errors.", result.errors, ); } return result.data; };

The numbers in the comments come from running the shopping chatbot demo above. The pattern works for the same reason it works in application and at AutoBeΒ  β€” typia’s validator gives the LLM enough detail (path, expected type, actual value) to fix its own mistakes.

Some LLM providers don’t even honor all the JSON Schema constraint keywords. The validation feedback channel works around that too: the model doesn’t have to understand "format": "uuid" β€” it just sees expected: "string & Format<\"uuid\">" in the error and tries again. The keywords most commonly ignored by providers, grouped by base type:

  • string β€” minLength, maxLength, pattern, format, contentMediaType
  • number β€” minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
  • array β€” minItems, maxItems, uniqueItems, items

typia checks every one of these at runtime and surfaces failures into the feedback channel β€” so whether or not the provider taught the model to honor the schema keyword in the first place, the model gets a chance to fix the value on the next turn.

ComponentstypiaTypeBoxajvio-tszodC.V.
Easy to useβœ…βŒβŒβŒβŒβŒ
Object (simple)Β βœ”βœ”βœ”βœ”βœ”βœ”
Object (hierarchical)Β βœ”βœ”βœ”βœ”βœ”βœ”
Object (recursive)Β βœ”βŒβœ”βœ”βœ”βœ”
Object (union, implicit)Β βœ…βŒβŒβŒβŒβŒ
Object (union, explicit)Β βœ”βœ”βœ”βœ”βœ”βŒ
Object (additional tags)Β βœ”βœ”βœ”βœ”βœ”βœ”
Object (template literal types)Β βœ”βœ”βœ”βŒβŒβŒ
Object (dynamic properties)Β βœ”βœ”βœ”βŒβŒβŒ
Array (rest tuple)Β βœ…βŒβŒβŒβŒβŒ
Array (hierarchical)Β βœ”βœ”βœ”βœ”βœ”βœ”
Array (recursive)Β βœ”βœ”βœ”βœ”βœ”βŒ
Array (recursive, union)Β βœ”βœ”βŒβœ”βœ”βŒ
Array (R+U, implicit)Β βœ…βŒβŒβŒβŒβŒ
Array (repeated)Β βœ…βŒβŒβŒβŒβŒ
Array (repeated, union)Β βœ…βŒβŒβŒβŒβŒ
Ultimate Union Typeβœ…βŒβŒβŒβŒβŒ

C.V. means class-validator

OpenAPI pipeline

Agentica accepts Swagger 2.0 / OpenAPI 3.0 / 3.1. The conversion goes through an intermediate β€œemended” 3.1 representation that strips out the ambiguity and duplication of the public OpenAPI specs, then to a migration schema, and finally to whichever LLM provider’s function-calling format you target.

Why the intermediate step? Without it, you’d need nΓ—m converters (every OpenAPI version Γ— every LLM provider). With it, you need n+m.

Where to go next

Last updated on