TypeScript Class
undefined
import { Agentica, IAgenticaController } from "@agentica/core";
import OpenAI from "openai";
import typia from "typia";
const agent = new Agentica({
vendor: {
api: new OpenAI({ apiKey: "********" }),
model: "gpt-4o-mini",
},
controllers: [
{
protocol: "class",
name: "bbs",
application: typia.llm.application<BbsArticleService>(),
execute: new BbsArticleService(),
} satisfies IAgenticaController.IClass,
],
});
await agent.conversate("What you can do?");TypeScript Class Functions for LLM Function Calling.
In @agentica/core, there is a concept of controller, a set of LLM (Large Language Model) function calling schemas, and executor for actual function calling. And @agentica/core supports three protocol types of the controllers; HTTP Restful API, MCP (Model Context Protocol), and TypeScript class.
When you want to serve functions from a TypeScript class, create an LLM application schema from typia.llm.application<Class>() function. And provide the class instance for the actual function calling.
If you compose a TypeScript class controller of above BbsArticleService class, you can start conversation with the BbsArticleService class like below demonstration video.
Documentation Strategy
Function Description
import { tags } from "typia";
import { IPage } from "@samchon/shopping-api/lib/structures/common/IPage";
import { IShoppingActorEntity } from "@samchon/shopping-api/lib/structures/shoppings/actors/IShoppingActorEntity";
import { IShoppingSale } from "@samchon/shopping-api/lib/structures/shoppings/sales/IShoppingSale";
export class ShoppingSaleService {
/**
* List up every summarized sales.
*
* List up every {@link IShoppingSale.ISummary summarized sales}.
*
* As you can see, returned sales are summarized, not detailed. It does not
* contain the SKU (Stock Keeping Unit) information represented by the
* {@link IShoppingSaleUnitOption} and {@link IShoppingSaleUnitStock} types.
* If you want to get such detailed information of a sale, use
* `GET /shoppings/customers/sales/{id}` operation for each sale.
*
* > If you're an A.I. chatbot, and the user wants to buy or compose
* > {@link IShoppingCartCommodity shopping cart} from a sale, please
* > call the `GET /shoppings/customers/sales/{id}` operation at least once
* > to the target sale to get detailed SKU information about the sale.
* > It needs to be run at least once for the next steps.
*
* @returns Paginated sales with summarized information
*/
@TypedRoute.Patch()
public index(props: {
/**
* Request info of pagination, searching and sorting
*/
input: IShoppingSale.IRequest,
}): Promise<IPage<IShoppingSale.ISummary>>;
/**
* Get a sale with detailed information.
*
* Get a {@link IShoppingSale sale} with detailed information including
* the SKU (Stock Keeping Unit) information represented by the
* {@link IShoppingSaleUnitOption} and {@link IShoppingSaleUnitStock} types.
*
* > If you're an A.I. chatbot, and the user wants to buy or compose a
* > {@link IShoppingCartCommodity shopping cart} from a sale, please call
* > this operation at least once to the target sale to get detailed SKU
* > information about the sale.
* >
* > It needs to be run at least once for the next steps. In other words,
* > if you A.I. agent has called this operation to a specific sale, you
* > don't need to call this operation again for the same sale.
* >
* > Additionally, please do not summarize the SKU information. Just show
* > the every options and stocks in the sale with detailed information.
*
* @returns Detailed sale information
*/
@TypedRoute.Get(":id")
public at(props: {
/**
* Target sale's {@link IShoppingSale.id}
*/
id: string & tags.Format<"uuid">,
}): Promise<IShoppingSale>;
}Describe purpose of each function with descriiption comment.
In the @agenticaβs internal #Orchestration Strategy, candidate functions are determined by referencing the function name and description. So, if youβre serving a lot of functions, you need to describe the purpose of each function detaily with description comments.
If thereβre relationships between some functions, like prerequisite or postrequisite conditions, also describe the relationships in the description comment.
DTO Description
undefined
/**
* Final component information on units for sale.
*
* `IShoppingSaleUnitStock` is a subsidiary entity of {@link IShoppingSaleUnit}
* that represents a product catalog for sale, and is a kind of final stock that is
* constructed by selecting all {@link IShoppingSaleUnitSelectableOption options}
* (variable "select" type) and their
* {@link IShoppingSaleUnitOptionCandidate candidate} values in the belonging unit.
* It is the "good" itself that customers actually purchase.
*
* - Product Name) MacBook
* - Options
* - CPU: { i3, i5, i7, i9 }
* - RAM: { 8GB, 16GB, 32GB, 64GB, 96GB }
* - SSD: { 256GB, 512GB, 1TB }
* - Number of final stocks: 4 * 5 * 3 = 60
*
* For reference, the total number of `IShoppingSaleUnitStock` records in an
* attribution unit can be obtained using Cartesian Product. In other words, the
* value obtained by multiplying all the candidate values that each
* (variable "select" type) option can have by the number of cases is the total
* number of final stocks in the unit.
*
* Of course, without a single variable "select" type option, the final stocks
* count in the unit is only 1.
*
* @author Samchon
*/
export interface IShoppingSaleUnitStock { ... }Describe within framework of entity.
When documenting DTO (Data Transfer Object) types, it would better to describe them within framework of entity. Try to describe conceptually what each DTO type represents, and explain what the related types (entities) are. When explaining the related types, using {@link Target} tag is recommended.
In actually, above DTO types are come from @samchon/shopping-backend, and their descriptions are similar with ERD (Entity Relationship Diagram) manualΒ explaining entities. If you open the ERD manual and read the description of shopping_sale_snapshot_unit_stocks entity, you may easily understand what I am talking about.
Also, if concept of a DTO type is difficult to understand for human, it is difficult for AI agent too. In that case, it would better to use some examples or analogies. For example, above IShoppingSaleUnitStock contains SKU (Stock Keeping Unit) concept, and it is hard to understand for someone who is not an e-commerce professional. So, IShoppingSaleUnitStock is explaining the SKU concept with an example of MacBookβs (CPU, RAM, and SSD) options.
Property Description
undefined
import { tags } from "typia";
/**
* Restriction information of the coupon.
*
* @author Samchon
*/
export interface IShoppingCouponRestriction {
/**
* Access level of coupon.
*
* - public: possible to find from public API
* - private: unable to find from public API
* - arbitrarily assigned by the seller or administrator
* - issued from one-time link
*/
access: "public" | "private";
/**
* Exclusivity or not.
*
* An exclusive discount coupon refers to a discount coupon that has an
* exclusive relationship with other discount coupons and can only be
* used alone. That is, when an exclusive discount coupon is used, no
* other discount coupon can be used for the same
* {@link IShoppingOrder order} or {@link IShoppingOrderGood good}.
*
* Please note that this exclusive attribute is a very different concept
* from multiplicative, which means whether the same coupon can be
* multiplied and applied to multiple coupons of the same order, so
* please do not confuse them.
*/
exclusive: boolean;
/**
* Limited quantity issued.
*
* If there is a limit to the quantity issued, it becomes impossible
* to issue tickets exceeding this value.
*
* In other words, the concept of N coupons being issued on
* a first-come, first-served basis is created.
*/
volume: null | (number & tags.Type<"uint32">);
/**
* Limited quantity issued per person.
*
* As a limit to the total amount of issuance per person, it is
* common to assign 1 to limit duplicate issuance to the same citizen,
* or to use the NULL value to set no limit.
*
* Of course, by assigning a value of N, the total amount issued
* to the same citizen can be limited.
*/
volume_per_citizen: null | (number & tags.Type<"uint32">);
/**
* Expiration day(s) value.
*
* The concept of expiring N days after a discount coupon ticket is issued.
*
* Therefore, customers must use the ticket within N days, if possible,
* from the time it is issued.
*/
expired_in: null | (number & tags.Type<"uint32">);
/**
* Expiration date.
*
* A concept that expires after YYYY-MM-DD after a discount coupon ticket
* is issued.
*
* Double restrictions are possible with expired_in, of which the one
* with the shorter expiration date is used.
*/
expired_at: null | (string & tags.Format<"date-time">);
}Describe what value must be filled in the property.
When documenting DTO properties, concentrate on what value must be filled in the property. In other words, describe the propertyβs purpose and the valueβs meaning with its domain restrictions. IShoppingCouponRestriction is a good example describing the domain restrictions.
Also, if target property is a reference to another entity by foreign key, it would better to utilizing the {@link target} tag. Looking at above example type IShoppingCartCommodityStock, it has a property stock_id which is a foreign key to the IShoppingSaleUnitStock entity with {@link target} tag.
Namespace Strategy
undefined
import { tags } from "typia";
/**
* Article entity.
*
* `IBbsArticle` is an entity representing an article in the BBS (Bulletin Board System).
*/
export 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">;
}
export 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>;
}typia.llm.application<App>() and HttpLlm.application() function copy the parent namespaced type description comment to the children types. @agentica calls this comment writing strategy as namespace documentation, and it is recommended for the efficient documentation.
As you can see from the above example, BbsArticleService has many CRUD functions about the IBbsArticle namespaced type. By the way, the above IBbsArticle.ICreate and IBbsArticle.IUpdate types are not repeating same description comments about the IBbsArticle type. Instead, just writing the short description comment about them, and just compose the LLM function calling application schema.
In that case, the IBbsArticle typeβs description comment would be copied to the IBbsArticle.ICreate and IBbsArticle.IUpdate types like above βConsole Outputβ case. Itβs a good strategy to avoid repeating same description comments, and also to deliver enough information to the LLM function calling.
Validation Feedback
undefined
import { ILlmSchema } from "./ILlmSchema";
import { IValidation } from "./IValidation";
/**
* LLM function metadata.
*
* `ILlmFunction` is an interface representing a function metadata, which has
* been used for the LLM (Language Large Model) function calling. Also, it's a
* function structure containing the function {@link name}, {@link parameters} and
* {@link output return type}.
*
* If you provide this `ILlmFunction` data to the LLM provider like "OpenAI",
* the "OpenAI" will compose a function arguments by analyzing conversations
* with the user. With the LLM composed arguments, you can execute the function
* and get the result.
*
* By the way, do not ensure that LLM will always provide the correct arguments.
* The LLM of present age is not perfect, so that you would better to validate
* the arguments before executing the function. I recommend you to validate the
* arguments before execution by using
* [`typia`](https://github.com/samchon/typia) library.
*
* @author Jeongho Nam - https://github.com/samchon
* @reference https://platform.openai.com/docs/guides/function-calling
*/
export interface ILlmFunction {
/**
* Representative name of the function.
*
* @maxLength 64
*/
name: string;
/** List of parameter types. */
parameters: ILlmSchema.IParameters;
/**
* Collection of separated parameters.
*
* Filled only when {@link ILlmApplication.IConfig.separate} is configured.
*/
separated?: ILlmFunction.ISeparated;
/**
* Expected return type.
*
* If the function returns nothing (`void`), the `output` value would be
* `undefined`.
*/
output?: ILlmSchema | undefined;
/**
* Description of the function.
*
* For reference, the `description` is a critical property for teaching the
* purpose of the function to LLMs (Large Language Models). LLMs use this
* description to determine which function to call.
*
* Also, when the LLM converses with the user, the `description` explains the
* function to the user. Therefore, the `description` property has the highest
* priority and should be carefully considered.
*/
description?: string | undefined;
/**
* Whether the function is deprecated or not.
*
* If the `deprecated` is `true`, the function is not recommended to use.
*
* LLM (Large Language Model) may not use the deprecated function.
*/
deprecated?: boolean | undefined;
/**
* Category tags for the function.
*
* You can fill this property by the `@tag ${name}` comment tag.
*/
tags?: string[] | undefined;
/**
* Validate function of the arguments.
*
* You know what? LLM (Large Language Model) like OpenAI takes a lot of
* mistakes when composing arguments in function calling. Even though `number`
* like simple type is defined in the {@link parameters} schema, LLM often
* fills it just by a `string` typed value.
*
* In that case, you have to give a validation feedback to the LLM by using
* this `validate` function. The `validate` function will return detailed
* information about every type errors about the arguments.
*
* And in my experience, OpenAI's `gpt-4o-mini` model tends to construct an
* invalid function calling arguments at the first trial about 50% of the
* time. However, if correct it through this `validate` function, the success
* rate soars to 99% at the second trial, and I've never failed at the third
* trial.
*
* > If you've {@link separated} parameters, use the
* > {@link ILlmFunction.ISeparated.validate} function instead when validating
* > the LLM composed arguments.
*
* > In that case, This `validate` function would be meaningful only when you've
* > merged the LLM and human composed arguments by
* > {@link HttpLlm.mergeParameters} function.
*
* @param args Arguments to validate
* @returns Validation result
*/
validate: (args: unknown) => IValidation<unknown>;
}
export namespace ILlmFunction {
/** Collection of separated parameters. */
export interface ISeparated {
/**
* Parameters that would be composed by the LLM.
*
* Even though no property exists in the LLM side, the `llm` property would
* have at least empty object type.
*/
llm: ILlmSchema.IParameters;
/** Parameters that would be composed by the human. */
human: ILlmSchema.IParameters | null;
/**
* Validate function of the separated arguments.
*
* If LLM part of separated parameters has some properties, this `validate`
* function will be filled for the {@link llm} type validation.
*
* > You know what? LLM (Large Language Model) like OpenAI takes a lot of
* > mistakes when composing arguments in function calling. Even though
* > `number` like simple type is defined in the {@link parameters} schema, LLM
* > often fills it just by a `string` typed value.
*
* > In that case, you have to give a validation feedback to the LLM by using
* > this `validate` function. The `validate` function will return detailed
* > information about every type errors about the arguments.
*
* > And in my experience, OpenAI's `gpt-4o-mini` model tends to construct an
* > invalid function calling arguments at the first trial about 50% of the
* > time. However, if correct it through this `validate` function, the
* > success rate soars to 99% at the second trial, and I've never failed at
* > the third trial.
*
* @param args Arguments to validate
* @returns Validate result
*/
validate?: ((args: unknown) => IValidation<unknown>) | undefined;
}
}typia.llm.application<Class>() embeds #Validation Feedback Strategy.
You know what? LLM (Large Language Model) like OpenAI takes a lot of mistakes when commposing arguments in the function calling. Even though number like simple type is defined in the parameters schema, LLM often fills it just by a string typed value.
To correct such LLM function calling mistakes, @agentica is running a validation feedback strategy which informs the validation errors to the AI agent, so that induce the AI agent to correct the mistakes at the next trial.
And when you compose LLM function calling schemas from the typia.llm.application<Class>() function, ILlmFunction.validate() functions for each parameters are constructed at the same time, and they are used for the validation feedback correcting mistakes of the LLM function calling.
Name Status ObjectConstraint1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ObjectFunctionSchema2οΈβ£2οΈβ£4οΈβ£2οΈβ£2οΈβ£2οΈβ£2οΈβ£2οΈβ£5οΈβ£2οΈβ£ ObjectHierarchical1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£2οΈβ£1οΈβ£1οΈβ£2οΈβ£ ObjectJsonSchema1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ObjectSimple1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ObjectUnionExplicit1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ObjectUnionImplicit1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ShoppingCartCommodity1οΈβ£2οΈβ£2οΈβ£3οΈβ£1οΈβ£1οΈβ£4οΈβ£2οΈβ£1οΈβ£2οΈβ£ ShoppingOrderCreate1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ShoppingOrderPublish1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£β1οΈβ£1οΈβ£1οΈβ£ ShoppingSaleDetail1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ ShoppingSalePage1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£1οΈβ£ Benchmark on OpenAIβs
o3-minimodel.The number 1 means the function calling succeeded at the first trial without validation feedback. And other number means that validation feedback was done to correct the mistakes.
Restrictions
import { ILlmApplication } from "@samchon/openapi";
import typia, { tags } from "typia";
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, or undefined if failed
*/
create(props: {
/**
* Information of the article to create
*/
input: IBbsArticle.ICreate;
}): Promise<IBbsArticle | undefined>;
/**
* Erase an article.
*
* @warning Cannot convert to LLM function schema
* Its because the parameter is not an object type
*/
erase(id: string & tags.Format<"uuid">): Promise<void>;
}Terminalsrc/examples/llm.application.violation.ts:4:41 - error TS(typia.llm.application): unsupported type detected - BbsArticleController.create: unknown - LLM application's function ("create")'s return type must not be union type with undefined. - BbsArticleController.erase: unknown - LLM application's function ("erase")'s parameter must be an object type. 4 const app: ILlmApplication = typia.llm.application< ~~~~~~~~~~~~~~~~~~~~~~ 5 BbsArticleController ~~~~~~~~~~~~~~~~~~~~~~~ 6 >(); ~~~
Only one parameter with object type.
You know what? Functions delivering to LLM must have only one parameter of object type. Itβs the LLM function callingβs rule. If you define a function with multiple parameters, or non object type, you will get an compilation error message like above.
Also, @agentica does not allow a function of returning an union type with undefined. It is because JSON schema cannot express the undefindable return type. If you want to make a function returning an union type with undefined, wrap it to an object type please. If do not so, you will get a compilation error message like above too.