Skip to content

Commit

Permalink
Multiple applications support by @nestia/agent.
Browse files Browse the repository at this point in the history
From now on, `@nestia/agent` supports multiple swagger files and typescript classes at the same time.
  • Loading branch information
samchon committed Jan 10, 2025
1 parent acafba9 commit e1cc217
Show file tree
Hide file tree
Showing 71 changed files with 2,577 additions and 1,490 deletions.
28 changes: 0 additions & 28 deletions packages/agent/.eslintrc.cjs

This file was deleted.

6 changes: 3 additions & 3 deletions packages/agent/build/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ const main = async (): Promise<void> => {
.trim();
}
await fs.promises.writeFile(
`${__dirname}/../src/internal/NestiaChatAgentSystemPrompt.ts`,
`${__dirname}/../src/internal/NestiaAgentSystemPrompt.ts`,
[
`export namespace NestiaChatAgentSystemPrompt {`,
`export namespace NestiaAgentSystemPrompt {`,
...Object.entries(record).map(
([key, value]) =>
` export const ${key.toUpperCase()} = ${JSON.stringify(value)};`,
` export const ${key.toUpperCase()} =\n ${JSON.stringify(value)};`,
),
`}`,
"",
Expand Down
21 changes: 21 additions & 0 deletions packages/agent/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"no-empty-pattern": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-namespace": "off",
},
},
);
13 changes: 8 additions & 5 deletions packages/agent/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/agent",
"version": "0.2.3",
"version": "0.3.0",
"main": "lib/index.js",
"module": "lib/index.mjs",
"typings": "lib/index.d.ts",
Expand All @@ -11,7 +11,8 @@
"build:main": "rimraf lib && npm run build:prompt && tsc && rollup -c",
"build:test": "rimraf bin && tsc -p test/tsconfig.json",
"build:prompt": "ts-node build/prompt.ts",
"dev": "npm run build:prompt && npm run build:test -- --watch"
"dev": "npm run build:prompt && npm run build:test -- --watch",
"eslint": "eslint ./**/*.ts"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -39,30 +40,32 @@
"LICENSE",
"package.json",
"lib",
"prompts",
"src"
],
"devDependencies": {
"@eslint/js": "^9.17.0",
"@nestia/e2e": "^0.7.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@samchon/shopping-api": "^0.11.0",
"@types/node": "^22.10.5",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"chalk": "4.1.2",
"dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0",
"eslint": "^9.17.0",
"rimraf": "^6.0.1",
"rollup": "^4.29.1",
"ts-node": "^10.9.2",
"ts-patch": "^3.3.0",
"tstl": "^3.0.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.18.2",
"typescript-transform-paths": "^3.5.3"
},
"dependencies": {
"@samchon/openapi": "^2.3.2",
"@samchon/openapi": "^2.3.4",
"openai": "^4.77.0",
"typia": "^7.5.1",
"uuid": "^11.0.4"
Expand Down
229 changes: 229 additions & 0 deletions packages/agent/src/NestiaAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import OpenAI from "openai";

import { ChatGptAgent } from "./chatgpt/ChatGptAgent";
import { NestiaAgentCostAggregator } from "./internal/NestiaAgentCostAggregator";
import { NestiaAgentOperationComposer } from "./internal/NestiaAgentOperationComposer";
import { NestiaAgentPromptTransformer } from "./internal/NestiaAgentPromptTransformer";
import { __map_take } from "./internal/__map_take";
import { INestiaAgentEvent } from "./structures/INestiaAgentEvent";
import { INestiaAgentOperationCollection } from "./structures/INestiaAgentOperationCollection";
import { INestiaAgentOperationSelection } from "./structures/INestiaAgentOperationSelection";
import { INestiaAgentPrompt } from "./structures/INestiaAgentPrompt";
import { INestiaAgentProps } from "./structures/INestiaAgentProps";
import { INestiaAgentTokenUsage } from "./structures/INestiaAgentTokenUsage";

/**
* Nestia A.I. chatbot agent.
*
* `NestiaChatAgent` is a facade class for the A.I. chatbot agent
* which performs the {@link converstate user's conversation function}
* with LLM (Large Language Model) function calling and manages the
* {@link getPromptHistories prompt histories}.
*
* @author Jeongho Nam - https://github.com/samchon
*/
export class NestiaAgent {
// THE OPERATIONS
private readonly operations_: INestiaAgentOperationCollection;

// STACK
private readonly stack_: INestiaAgentOperationSelection[];
private readonly prompt_histories_: INestiaAgentPrompt[];
private readonly listeners_: Map<string, Set<Function>>;

// STATUS
private readonly token_usage_: INestiaAgentTokenUsage;
private ready_: boolean;

/* -----------------------------------------------------------
CONSTRUCTOR
----------------------------------------------------------- */
/**
* Initializer constructor.
*
* @param props Properties to construct the agent
*/
public constructor(private readonly props: INestiaAgentProps) {
// OPERATIONS
this.operations_ = NestiaAgentOperationComposer.compose({
controllers: props.controllers,
config: props.config,
});

// STATUS
this.stack_ = [];
this.listeners_ = new Map();
this.prompt_histories_ = (props.histories ?? []).map((input) =>
NestiaAgentPromptTransformer.transform({
operations: this.operations_.group,
input,
}),
);

// STATUS
this.token_usage_ = {
total: 0,
prompt: {
total: 0,
audio: 0,
cached: 0,
},
completion: {
total: 0,
accepted_prediction: 0,
audio: 0,
reasoning: 0,
rejected_prediction: 0,
},
};
this.ready_ = false;
}

/* -----------------------------------------------------------
ACCESSORS
----------------------------------------------------------- */
/**
* Conversate with the A.I. chatbot.
*
* User talks to the A.I. chatbot with the content.
*
* When the user's conversation implies the A.I. chatbot to execute a
* function calling, the returned chat prompts will contain the
* function calling information like {@link INestiaAgentPrompt.IExecute}.
*
* @param content The content to talk
* @returns List of newly created chat prompts
*/
public async conversate(content: string): Promise<INestiaAgentPrompt[]> {
const prompt: INestiaAgentPrompt.IText = {
type: "text",
role: "user",
text: content,
};
const newbie: INestiaAgentPrompt[] = await ChatGptAgent.execute({
// APPLICATION
operations: this.operations_,
config: this.props.config,

// STATES
histories: this.prompt_histories_,
stack: this.stack_,
ready: () => this.ready_,
prompt,

// HANDLERS
dispatch: (event) => this.dispatch(event),
request: async (source, body) => {
// request information
const event: INestiaAgentEvent.IRequest = {
type: "request",
source,
body: {
...body,
model: this.props.provider.model,
},
options: this.props.provider.options,
};
await this.dispatch(event);

// completion
const value: OpenAI.ChatCompletion =
await this.props.provider.api.chat.completions.create(
event.body,
event.options,
);
NestiaAgentCostAggregator.aggregate(this.token_usage_, value);
await this.dispatch({
type: "response",
source,
body: event.body,
options: event.options,
value,
});
return value;
},
initialize: async () => {
this.ready_ = true;
await this.dispatch({
type: "initialize",
});
},
});
this.prompt_histories_.push(prompt, ...newbie);
return [prompt, ...newbie];
}

/**
* Get the chatbot's prompt histories.
*
* Get list of chat prompts that the chatbot has been conversated.
*
* @returns List of chat prompts
*/
public getPromptHistories(): INestiaAgentPrompt[] {
return this.prompt_histories_;
}

/**
* Get token usage of the A.I. chatbot.
*
* Entire token usage of the A.I. chatbot during the conversating
* with the user by {@link conversate} method callings.
*
* @returns Cost of the A.I. chatbot
*/
public getTokenUsage(): INestiaAgentTokenUsage {
return this.token_usage_;
}

/* -----------------------------------------------------------
EVENT HANDLERS
----------------------------------------------------------- */
/**
* Add an event listener.
*
* Add an event listener to be called whenever the event is emitted.
*
* @param type Type of event
* @param listener Callback function to be called whenever the event is emitted
*/
public on<Type extends INestiaAgentEvent.Type>(
type: Type,
listener: (event: INestiaAgentEvent.Mapper[Type]) => void | Promise<void>,
): void {
__map_take(this.listeners_, type, () => new Set()).add(listener);
}

/**
* Erase an event listener.
*
* Erase an event listener to stop calling the callback function.
*
* @param type Type of event
* @param listener Callback function to erase
*/
public off<Type extends INestiaAgentEvent.Type>(
type: Type,
listener: (event: INestiaAgentEvent.Mapper[Type]) => void | Promise<void>,
): void {
const set: Set<Function> | undefined = this.listeners_.get(type);
if (set) {
set.delete(listener);
if (set.size === 0) this.listeners_.delete(type);
}
}

private async dispatch<Event extends INestiaAgentEvent>(
event: Event,
): Promise<void> {
const set: Set<Function> | undefined = this.listeners_.get(event.type);
if (set)
await Promise.all(
Array.from(set).map(async (listener) => {
try {
await listener(event);
} catch {}
}),
);
}
}
Loading

0 comments on commit e1cc217

Please sign in to comment.