fix function calling + add clear all on chats

This commit is contained in:
Emily
2024-12-17 16:25:27 +01:00
parent 0a9474d00c
commit 68d362d1b3
6 changed files with 174 additions and 82 deletions

View File

@@ -77,6 +77,8 @@ async function sendMessage() {
if (loading.value) return;
if (!project.value) return;
if (currentText.value.length == 0) return;
loading.value = true;
const body: any = { text: currentText.value, timeOffset: new Date().getTimezoneOffset() }
@@ -194,6 +196,15 @@ async function deleteChat(chat_id: string) {
const { visible: pricingDrawerVisible } = usePricingDrawer()
async function clearAllChats() {
const sure = confirm(`Are you sure to delete all ${(chatsList.value?.length || 0)} chats ?`);
if (!sure) return;
await $fetch(`/api/ai/delete_all_chats`, {
headers: useComputedHeaders({ useSnapshotDates: false }).value
});
await reloadChatsList();
}
</script>
@@ -343,7 +354,12 @@ const { visible: pricingDrawerVisible } = usePricingDrawer()
</LyxUiButton>
</div>
<div class="poppins font-semibold text-[1.1rem]"> History </div>
<div class="flex items-center gap-4">
<div class="poppins font-semibold text-[1.1rem]"> History </div>
<LyxUiButton v-if="chatsList && chatsList.length > 0" @click="clearAllChats()" type="secondary" class="text-center text-[.8rem]">
Clear all
</LyxUiButton>
</div>
<div class="px-2">
<div @click="openChat()"

View File

@@ -1,8 +1,8 @@
import { EventModel } from "@schema/metrics/EventSchema";
import { AdvancedTimelineAggregationOptions, executeAdvancedTimelineAggregation, executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService";
import { executeTimelineAggregation } from "~/server/services/TimelineService";
import { Types } from "mongoose";
import { AIPlugin, AIPlugin_TTool } from "../Plugin";
import dayjs from 'dayjs';
const getEventsCountTool: AIPlugin_TTool<'getEventsCount'> = {
type: 'function',
@@ -46,12 +46,12 @@ export class AiEvents extends AIPlugin<['getEventsCount', 'getEventsTimeline']>
super({
'getEventsCount': {
handler: async (data: { project_id: string, from?: string, to?: string, name?: string, metadata?: string }) => {
handler: async (data: { project_id: string, from: string, to: string, name?: string, metadata?: string }) => {
const query: any = {
project_id: data.project_id,
created_at: {
$gt: data.from ? new Date(data.from).getTime() : new Date(2023).getTime(),
$lt: data.to ? new Date(data.to).getTime() : new Date().getTime(),
$gt: new Date(data.from),
$lt: new Date(data.to),
}
}
if (data.metadata) query.metadata = data.metadata;
@@ -62,21 +62,17 @@ export class AiEvents extends AIPlugin<['getEventsCount', 'getEventsTimeline']>
tool: getEventsCountTool
},
'getEventsTimeline': {
handler: async (data: { project_id: string, from: string, to: string, name?: string, metadata?: string }) => {
const query: AdvancedTimelineAggregationOptions & { customMatch: Record<string, any> } = {
projectId: new Types.ObjectId(data.project_id) as any,
model: EventModel,
from: dayjs(data.from).startOf('day').toISOString(),
to: dayjs(data.to).startOf('day').toISOString(),
slice: 'day',
customMatch: {}
}
if (data.metadata) query.customMatch.metadata = data.metadata;
if (data.name) query.customMatch.name = data.name;
handler: async (data: { project_id: string, from: string, to: string, time_offset: number, name?: string, metadata?: string }) => {
const timelineData = await executeAdvancedTimelineAggregation(query);
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to);
return { data: timelineFilledMerged };
const timelineData = await executeTimelineAggregation({
projectId: new Types.ObjectId(data.project_id),
model: EventModel,
from: data.from,
to: data.to,
slice: 'day',
timeOffset: data.time_offset
});
return { data: timelineData };
},
tool: getEventsTimelineTool
}

View File

@@ -0,0 +1,86 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { executeTimelineAggregation } from "~/server/services/TimelineService";
import { Types } from "mongoose";
import { AIPlugin, AIPlugin_TTool } from "../Plugin";
import { SessionModel } from "@schema/metrics/SessionSchema";
const getSessionsCountsTool: AIPlugin_TTool<'getSessionsCount'> = {
type: 'function',
function: {
name: 'getSessionsCount',
description: 'Gets the number of sessions received on a date range',
parameters: {
type: 'object',
properties: {
from: { type: 'string', description: 'ISO string of start date' },
to: { type: 'string', description: 'ISO string of end date' },
min_duration: { type: 'number', description: 'Minimum duration of the session' },
max_duration: { type: 'number', description: 'Maximum duration of the session' },
},
required: ['from', 'to']
}
}
}
const getSessionsTimelineTool: AIPlugin_TTool<'getSessionsTimeline'> = {
type: 'function',
function: {
name: 'getSessionsTimeline',
description: 'Gets an array of date and count for events received on a date range. Should be used to create charts.',
parameters: {
type: 'object',
properties: {
from: { type: 'string', description: 'ISO string of start date' },
to: { type: 'string', description: 'ISO string of end date' },
},
required: ['from', 'to']
}
}
}
export class AiSessions extends AIPlugin<['getSessionsCount', 'getSessionsTimeline']> {
constructor() {
super({
'getSessionsCount': {
handler: async (data: { project_id: string, from: string, to: string, min_duration?: number, max_duration?: number }) => {
const query: any = {
project_id: data.project_id,
created_at: {
$gt: new Date(data.from),
$lt: new Date(data.to),
},
duration: {
$gte: data.min_duration || 0,
$lte: data.max_duration || 999_999_999,
}
}
const result = await VisitModel.countDocuments(query);
return { count: result };
},
tool: getSessionsCountsTool
},
'getSessionsTimeline': {
handler: async (data: { project_id: string, from: string, to: string, time_offset: number, website?: string, page?: string }) => {
const timelineData = await executeTimelineAggregation({
projectId: new Types.ObjectId(data.project_id),
model: SessionModel,
from: data.from,
to: data.to,
slice: 'day',
timeOffset: data.time_offset
});
return { data: timelineData };
},
tool: getSessionsTimelineTool
}
})
}
}
export const ASessionsInstance = new AiSessions();

View File

@@ -1,13 +1,7 @@
import { VisitModel } from "@schema/metrics/VisitSchema";
import { AdvancedTimelineAggregationOptions, executeAdvancedTimelineAggregation, executeTimelineAggregation, fillAndMergeTimelineAggregationV2 } from "~/server/services/TimelineService";
import { executeTimelineAggregation } from "~/server/services/TimelineService";
import { Types } from "mongoose";
import { AIPlugin, AIPlugin_TTool } from "../Plugin";
import dayjs from 'dayjs';
import { zodFunction } from "openai/helpers/zod";
import { z } from 'zod';
import { Slice } from "@services/DateService";
const getVisitsCountsTool: AIPlugin_TTool<'getVisitsCount'> = {
type: 'function',
@@ -39,11 +33,6 @@ const getVisitsTimelineTool: AIPlugin_TTool<'getVisitsTimeline'> = {
to: { type: 'string', description: 'ISO string of end date' },
website: { type: 'string', description: 'The website of the visits' },
page: { type: 'string', description: 'The page of the visit' },
slice: {
type: 'string',
description: 'The slice for the visit data',
enum: ['hour', 'day', 'month', 'year']
}
},
required: ['from', 'to']
}
@@ -56,13 +45,13 @@ export class AiVisits extends AIPlugin<['getVisitsCount', 'getVisitsTimeline']>
super({
'getVisitsCount': {
handler: async (data: { project_id: string, from?: string, to?: string, website?: string, page?: string }) => {
handler: async (data: { project_id: string, from: string, to: string, website?: string, page?: string }) => {
const query: any = {
project_id: data.project_id,
created_at: {
$gt: data.from ? new Date(data.from).getTime() : new Date(2023).getTime(),
$lt: data.to ? new Date(data.to).getTime() : new Date().getTime(),
$gt: new Date(data.from),
$lt: new Date(data.to),
}
}
@@ -78,33 +67,17 @@ export class AiVisits extends AIPlugin<['getVisitsCount', 'getVisitsTimeline']>
tool: getVisitsCountsTool
},
'getVisitsTimeline': {
handler: async (data: { project_id: string, from: string, to: string, time_offset: number, website?: string, page?: string, slice?: string }) => {
handler: async (data: { project_id: string, from: string, to: string, time_offset: number, website?: string, page?: string }) => {
const timelineData = await executeTimelineAggregation({
projectId: new Types.ObjectId(data.project_id),
model: VisitModel,
from: data.from,
to: data.to,
slice: (data.slice || 'day') as Slice,
slice: 'day',
timeOffset: data.time_offset
});
return { data: timelineData };
// const query: AdvancedTimelineAggregationOptions & { customMatch: Record<string, any> } = {
// projectId: new Types.ObjectId(data.project_id) as any,
// model: VisitModel,
// from: dayjs(data.from).startOf('day').toISOString(),
// to: dayjs(data.to).startOf('day').toISOString(),
// slice: 'day',
// customMatch: {}
// }
// if (data.website) query.customMatch.website = data.website;
// if (data.page) query.customMatch.page = data.page;
// const timelineData = await executeAdvancedTimelineAggregation(query);
// const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, 'day', data.from, data.to);
// return { data: timelineFilledMerged };
},
tool: getVisitsTimelineTool
}

View File

@@ -0,0 +1,13 @@
import { AiChatModel } from "@schema/ai/AiChatSchema";
export default defineEventHandler(async event => {
const data = await getRequestData(event);
if (!data) return;
const { project_id } = data;
const result = await AiChatModel.updateMany({ project_id }, { deleted: true });
return result.modifiedCount > 0;
});

View File

@@ -89,6 +89,9 @@ type ElaborateResponseCallbacks = {
async function elaborateResponse(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], pid: string, time_offset: number, chat_id: string, callbacks?: ElaborateResponseCallbacks) {
console.log('[ELABORATING RESPONSE]');
console.dir(messages, { depth: Infinity });
const responseStream = await openai.beta.chat.completions.stream({ model: OPENAI_MODEL, messages, n: 1, tools });
const functionCalls: FunctionCall[] = [];
@@ -103,50 +106,55 @@ async function elaborateResponse(messages: OpenAI.Chat.Completions.ChatCompletio
if (delta.content) await callbacks?.onDelta?.(delta.content);
if (delta.tool_calls) {
const toolCall = delta.tool_calls[0];
if (!toolCall.function) throw Error('Cannot get function from tool_calls');
const functionName = toolCall.function.name;
for (const toolCall of delta.tool_calls) {
const functionCall: FunctionCall = functionName ?
{ name: functionName, argsRaw: [], collecting: true, result: null, tool_call_id: toolCall.id as string } :
functionCalls.at(-1) as FunctionCall;
if (!toolCall.function) throw Error('Cannot get function from tool_calls');
if (functionName) functionCalls.push(functionCall);
const functionName = toolCall.function.name;
if (functionName) await callbacks?.onFunctionName?.(functionName);
const functionCall: FunctionCall = functionName ?
{ name: functionName, argsRaw: [], collecting: true, result: null, tool_call_id: toolCall.id as string } :
functionCalls.at(-1) as FunctionCall;
if (functionName) functionCalls.push(functionCall);
if (functionName) await callbacks?.onFunctionName?.(functionName);
if (toolCall.function.arguments) functionCall.argsRaw.push(toolCall.function.arguments);
}
if (toolCall.function.arguments) functionCall.argsRaw.push(toolCall.function.arguments);
}
if (finishReason === "tool_calls" && functionCalls.at(-1)?.collecting) {
const functionCall: FunctionCall = functionCalls.at(-1) as FunctionCall;
await callbacks?.onFunctionCall?.(functionCall.name);
const args = JSON.parse(functionCall.argsRaw.join(''));
for (const functionCall of functionCalls) {
await callbacks?.onFunctionCall?.(functionCall.name);
const args = JSON.parse(functionCall.argsRaw.join(''));
const functionResult = await functions[functionCall.name]({ project_id: pid, time_offset, ...args });
functionCall.result = functionResult;
await callbacks?.onFunctionResult?.(functionCall.name, functionResult);
const functionResult = await functions[functionCall.name]({ project_id: pid, time_offset, ...args });
functionCall.result = functionResult;
await callbacks?.onFunctionResult?.(functionCall.name, functionResult);
await addMessageToChat({
role: 'assistant',
content: delta.content,
refusal: delta.refusal,
tool_calls: [
{
id: functionCall.tool_call_id, type: 'function',
function: {
name: functionCall.name, arguments: functionCall.argsRaw.join('')
await addMessageToChat({
role: 'assistant',
content: delta.content,
refusal: delta.refusal,
tool_calls: [
{
id: functionCall.tool_call_id, type: 'function',
function: {
name: functionCall.name, arguments: functionCall.argsRaw.join('')
}
}
}
]
}, chat_id);
]
}, chat_id);
await addMessageToChat({ tool_call_id: functionCall.tool_call_id, role: 'tool', content: JSON.stringify(functionCall.result) }, chat_id);
await addMessageToChat({ tool_call_id: functionCall.tool_call_id, role: 'tool', content: JSON.stringify(functionCall.result) }, chat_id);
functionCall.collecting = false;
}
functionCall.collecting = false;
lastFinishReason = finishReason;
}