Add function calling
This commit is contained in:
parent
f3a74bc46c
commit
a5d6163ef9
13 changed files with 272 additions and 32 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "aslobot-matrix",
|
"name": "aslobot-matrix",
|
||||||
"version": "0.8.2",
|
"version": "0.9.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"api": {
|
"api": {
|
||||||
"key": ""
|
"key": ""
|
||||||
},
|
},
|
||||||
|
"password": "",
|
||||||
"personalities": [
|
"personalities": [
|
||||||
{
|
{
|
||||||
"personalityName": "puk",
|
"personalityName": "puk",
|
||||||
|
|
|
||||||
|
|
@ -59,19 +59,17 @@ const onAI = async (
|
||||||
|
|
||||||
personality = config.app.ai.personalities[state.personality.index];
|
personality = config.app.ai.personalities[state.personality.index];
|
||||||
|
|
||||||
let textMod = text.replace("!ai", "").trim().toLowerCase();
|
let textMod = text.replace("!ai", "").trim();
|
||||||
let instructions = {
|
let instructions = {
|
||||||
prefferedLanguages: ["english", "slovak"],
|
prefferedLanguages: ["english", "slovak"],
|
||||||
users: alts.map((alt) => ({
|
users: alts.map((alt) => ({
|
||||||
names: alt.keys,
|
names: alt.keys,
|
||||||
alt: alt.alt,
|
alt: alt.alt,
|
||||||
backstory: alt.id
|
|
||||||
? getUserById(`${alt.id}:${config.server}`).information
|
|
||||||
: undefined,
|
|
||||||
})),
|
})),
|
||||||
aiPersonality: personality?.personality ?? "",
|
aiPersonality: personality?.personality ?? "",
|
||||||
aiLikes: personality?.likes ?? "",
|
aiLikes: personality?.likes ?? "",
|
||||||
aiDislikes: personality?.dislikes ?? "",
|
aiDislikes: personality?.dislikes ?? "",
|
||||||
|
aiMemory: state.aiMemory ?? [],
|
||||||
} as IAIInstructions;
|
} as IAIInstructions;
|
||||||
|
|
||||||
const username = getUserName(user);
|
const username = getUserName(user);
|
||||||
|
|
@ -81,6 +79,11 @@ const onAI = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseAI = await getTextGemini(
|
const responseAI = await getTextGemini(
|
||||||
|
{
|
||||||
|
client: client,
|
||||||
|
roomId: roomId,
|
||||||
|
sender: sender,
|
||||||
|
},
|
||||||
instructions,
|
instructions,
|
||||||
`${username}: ${textMod}`,
|
`${username}: ${textMod}`,
|
||||||
`${repliedUsername}: ${repliedMessage}`,
|
`${repliedUsername}: ${repliedMessage}`,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
export * from "./ai.js";
|
export * from "./ai.js";
|
||||||
export * from "./types.js";
|
export * from "./types.js";
|
||||||
|
export * from "./alts.js";
|
||||||
|
export * from "./prices.js";
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ interface IAIInstructions {
|
||||||
aiPersonality: string;
|
aiPersonality: string;
|
||||||
aiLikes: string;
|
aiLikes: string;
|
||||||
aiDislikes: string;
|
aiDislikes: string;
|
||||||
|
aiMemory: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type IAIAlt, type IAIUser, type IAIInstructions };
|
export { type IAIAlt, type IAIUser, type IAIInstructions };
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@ const onHelp = (_text: string, roomId: string) => {
|
||||||
<li><b>!bowling {text}</b> - Repeats your message in bowling</li>
|
<li><b>!bowling {text}</b> - Repeats your message in bowling</li>
|
||||||
<li><b>!help</b> - Prints this help message</li>
|
<li><b>!help</b> - Prints this help message</li>
|
||||||
<li><b>!me / !me {mention}</b> - Prints data about you</li>
|
<li><b>!me / !me {mention}</b> - Prints data about you</li>
|
||||||
<li><b>!myinfo {newinfo}</b> - Updates your description</li>
|
|
||||||
<li><b>!leaderboard</b> - Prints total user ranking</li>
|
<li><b>!leaderboard</b> - Prints total user ranking</li>
|
||||||
<li><b>!aileaderboard</b> - Prints total user ai cost</li>
|
<li><b>!aileaderboard</b> - Prints total user ai cost</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,6 @@ const registerModuleUser = (
|
||||||
startConditions: [`${config.app.triggerPrefix}me`],
|
startConditions: [`${config.app.triggerPrefix}me`],
|
||||||
callbackFunc: onMe,
|
callbackFunc: onMe,
|
||||||
});
|
});
|
||||||
callbackStore.messageCallbacks.push({
|
|
||||||
startConditions: [`${config.app.triggerPrefix}myinfo `],
|
|
||||||
callbackFunc: onMyInfo,
|
|
||||||
});
|
|
||||||
callbackStore.messageCallbacks.push({
|
callbackStore.messageCallbacks.push({
|
||||||
startConditions: [`${config.app.triggerPrefix}leaderboard`],
|
startConditions: [`${config.app.triggerPrefix}leaderboard`],
|
||||||
callbackFunc: onLeaderboard,
|
callbackFunc: onLeaderboard,
|
||||||
|
|
@ -55,22 +51,6 @@ const onMe = (text: string, roomId: string, sender: string) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMyInfo = (text: string, roomId: string, sender: string) => {
|
|
||||||
const user = getUserById(sender);
|
|
||||||
const newInformation = text.replace(
|
|
||||||
`${config.app.triggerPrefix}myinfo `,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!user || newInformation.length < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.information = newInformation;
|
|
||||||
|
|
||||||
client.sendTextMessage(roomId, "Information updated");
|
|
||||||
};
|
|
||||||
|
|
||||||
const onLeaderboard = (_text: string, roomId: string) => {
|
const onLeaderboard = (_text: string, roomId: string) => {
|
||||||
const mapUsersToLeaderboard = (user: IUser): string => {
|
const mapUsersToLeaderboard = (user: IUser): string => {
|
||||||
const level = getLevel(user.experience);
|
const level = getLevel(user.experience);
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,123 @@
|
||||||
import { GoogleGenAI } from "@google/genai";
|
import { GoogleGenAI } from "@google/genai";
|
||||||
import { config } from "../../config.js";
|
import { config } from "../../config.js";
|
||||||
import type { AIResponseImage, AIResponseText } from "./types.js";
|
import type {
|
||||||
|
AIResponseImage,
|
||||||
|
AIResponseText,
|
||||||
|
AIToolMatrixData,
|
||||||
|
} from "./types.js";
|
||||||
import type { IAIInstructions } from "../../modules/ai/types.js";
|
import type { IAIInstructions } from "../../modules/ai/types.js";
|
||||||
|
import { FunctionCallingConfigMode } from "@google/genai";
|
||||||
|
import { toolFunctions, tools } from "./tools.js";
|
||||||
|
import type { FunctionResponse } from "@google/genai";
|
||||||
|
import type { Content } from "@google/genai";
|
||||||
|
|
||||||
const googleAI = new GoogleGenAI({
|
const googleAI = new GoogleGenAI({
|
||||||
apiKey: config.app.ai.api.key,
|
apiKey: config.app.ai.api.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTextGemini = async (
|
const getTextGemini = async (
|
||||||
|
matrixData: AIToolMatrixData,
|
||||||
instructions: IAIInstructions,
|
instructions: IAIInstructions,
|
||||||
input: string,
|
input: string,
|
||||||
oldInput?: string,
|
oldInput?: string,
|
||||||
): Promise<AIResponseText> => {
|
): Promise<AIResponseText> => {
|
||||||
|
const inputContent: Content = {
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: input,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const oldInputContent: Content = {
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: oldInput ?? "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const contents: Content[] = oldInput
|
||||||
|
? [oldInputContent, inputContent]
|
||||||
|
: [inputContent];
|
||||||
|
|
||||||
const response = await googleAI.models.generateContent({
|
const response = await googleAI.models.generateContent({
|
||||||
model: "gemini-3-flash-preview",
|
model: "gemini-3-flash-preview",
|
||||||
contents: oldInput ? [oldInput, input] : input,
|
contents: contents,
|
||||||
config: {
|
config: {
|
||||||
systemInstruction: JSON.stringify(instructions),
|
systemInstruction: JSON.stringify(instructions),
|
||||||
|
toolConfig: {
|
||||||
|
functionCallingConfig: {
|
||||||
|
mode: FunctionCallingConfigMode.AUTO,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tools: [{ functionDeclarations: tools }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let text = response.text ?? "AI Error";
|
||||||
|
let token = response.usageMetadata?.totalTokenCount ?? 0;
|
||||||
|
|
||||||
|
const content = response.candidates?.at(0)?.content;
|
||||||
|
const functionCall = content?.parts?.at(0)?.functionCall;
|
||||||
|
|
||||||
|
if (response.text || !content || !functionCall) {
|
||||||
|
return {
|
||||||
|
text: text,
|
||||||
|
tokens: token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
text = `Calling function ${functionCall.name}`;
|
||||||
|
|
||||||
|
const func = toolFunctions.find(
|
||||||
|
(func) => func.name === functionCall.name,
|
||||||
|
)?.function;
|
||||||
|
if (!func) {
|
||||||
|
return {
|
||||||
|
text: text,
|
||||||
|
tokens: token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = func(matrixData, functionCall.args);
|
||||||
|
const functionResponse: FunctionResponse = {
|
||||||
|
id: functionCall.id ?? "",
|
||||||
|
name: functionCall.name ?? "",
|
||||||
|
response: {
|
||||||
|
output: JSON.stringify(output),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseTool = await googleAI.models.generateContent({
|
||||||
|
model: "gemini-3-flash-preview",
|
||||||
|
contents: [
|
||||||
|
...contents,
|
||||||
|
content,
|
||||||
|
{
|
||||||
|
role: "tool",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
functionResponse: functionResponse,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
config: {
|
||||||
|
systemInstruction: JSON.stringify(instructions),
|
||||||
|
toolConfig: {
|
||||||
|
functionCallingConfig: {
|
||||||
|
mode: FunctionCallingConfigMode.AUTO,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tools: [{ functionDeclarations: tools }],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: response.text ?? "AI Error",
|
text: responseTool.text ?? "AI Error",
|
||||||
tokens: response.usageMetadata?.totalTokenCount ?? 0,
|
tokens: token + (responseTool.usageMetadata?.totalTokenCount ?? 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./ai.js";
|
export * from "./ai.js";
|
||||||
export * from "./types.js";
|
export * from "./types.js";
|
||||||
|
export * from "./tools.js";
|
||||||
|
|
|
||||||
139
src/services/ai/tools.ts
Normal file
139
src/services/ai/tools.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import type { FunctionDeclaration } from "@google/genai";
|
||||||
|
import { config, packageConfig } from "../../config.js";
|
||||||
|
import type { AIToolFunction } from "./types.js";
|
||||||
|
import { save, state } from "../../store/store.js";
|
||||||
|
|
||||||
|
const tools: FunctionDeclaration[] = [
|
||||||
|
{
|
||||||
|
name: "getBotInfo",
|
||||||
|
description: "Gets information about this AI bot",
|
||||||
|
parametersJsonSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "shutdownBot",
|
||||||
|
description: "Shuts/Turns this AI bot down",
|
||||||
|
parametersJsonSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
password: {
|
||||||
|
type: "string",
|
||||||
|
description: "shutdown password provided by the user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["password"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remember",
|
||||||
|
description: "Gets information about this AI bot",
|
||||||
|
parametersJsonSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
knowledge: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"new knowledge to remember and add to bot total knowledge",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["knowledge"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "changeUsername",
|
||||||
|
description: "Changes the username of this AI bot",
|
||||||
|
parametersJsonSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: "string",
|
||||||
|
description: "new username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["username"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getFullUserData",
|
||||||
|
description: "Gets the full user data about all users",
|
||||||
|
parametersJsonSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const toolFunctions: AIToolFunction[] = [
|
||||||
|
{
|
||||||
|
name: "getBotInfo",
|
||||||
|
function: () => {
|
||||||
|
return {
|
||||||
|
botInfo: `Aslobot version ${packageConfig.version}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "shutdownBot",
|
||||||
|
function: async (matrix, args) => {
|
||||||
|
if (args.password === config.app.ai.password) {
|
||||||
|
await matrix.client.sendTextMessage(
|
||||||
|
matrix.roomId,
|
||||||
|
"Saving data...",
|
||||||
|
);
|
||||||
|
save();
|
||||||
|
|
||||||
|
await matrix.client.sendTextMessage(
|
||||||
|
matrix.roomId,
|
||||||
|
"Shutting down...",
|
||||||
|
);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
message: "incorrect password",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remember",
|
||||||
|
function: (_matrix, args) => {
|
||||||
|
if (!state.aiMemory) {
|
||||||
|
state.aiMemory = [];
|
||||||
|
}
|
||||||
|
if (args.knowledge) {
|
||||||
|
state.aiMemory.push(args.knowledge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "success",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "changeUsername",
|
||||||
|
function: (matrix, args) => {
|
||||||
|
if (args.username) {
|
||||||
|
state.aiMemory.push(args.knowledge);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix.client.setDisplayName(args.username);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "success",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "getFullUserData",
|
||||||
|
function: (_matrix, _args) => {
|
||||||
|
return {
|
||||||
|
users: state.users,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { tools, toolFunctions };
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { MatrixClient } from "matrix-js-sdk";
|
||||||
|
|
||||||
interface AIResponseText {
|
interface AIResponseText {
|
||||||
text: string;
|
text: string;
|
||||||
tokens: number;
|
tokens: number;
|
||||||
|
|
@ -8,4 +10,20 @@ interface AIResponseImage {
|
||||||
tokens: number;
|
tokens: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type AIResponseText, type AIResponseImage };
|
interface AIToolFunction {
|
||||||
|
name: string;
|
||||||
|
function: (matrix: AIToolMatrixData, args: any) => object;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AIToolMatrixData {
|
||||||
|
client: MatrixClient;
|
||||||
|
roomId: string;
|
||||||
|
sender: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
type AIResponseText,
|
||||||
|
type AIResponseImage,
|
||||||
|
type AIToolFunction,
|
||||||
|
type AIToolMatrixData,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ let state: IState = {
|
||||||
index: 0,
|
index: 0,
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
},
|
},
|
||||||
|
aiMemory: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
interface IState {
|
interface IState {
|
||||||
users: IUser[];
|
users: IUser[];
|
||||||
personality: IPersonality;
|
personality: IPersonality;
|
||||||
|
aiMemory: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUser {
|
interface IUser {
|
||||||
id: string;
|
id: string;
|
||||||
information?: string;
|
|
||||||
role: TRole;
|
role: TRole;
|
||||||
experience: number;
|
experience: number;
|
||||||
money: number;
|
money: number;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue