Skip to content

Commit

Permalink
Llm chat core (#58)
Browse files Browse the repository at this point in the history
* Feat: enhance conversation data handling to include subtask completion status

* Feat: refactor conversation creation to use GET request

* Feat: update resource type in chatWithLLMByDocs function

* Update src/lib/utils/firestore.ts

Co-authored-by: JacobLinCool <[email protected]>

* feat: using promise to parallel create conversation

* fix: use user history rather than agent+user

* fix: fetch the summary on first load

---------

Co-authored-by: Takala <[email protected]>
Co-authored-by: JacobLinCool <[email protected]>
  • Loading branch information
3 people authored Dec 23, 2024
1 parent 12b9537 commit e933daa
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 82 deletions.
39 changes: 15 additions & 24 deletions src/lib/components/session/HostView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -111,32 +111,23 @@
async function handleStartSession() {
try {
// 為每個群組的個參與者創建對話
for (const group of $groups) {
for (const participant of group.participants) {
const response = await fetch(
`/api/session/${$page.params.id}/group/${group.id}/conversations`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
task: $session?.task || '',
subtasks: $session?.subtasks || [],
resources: $session?.resources.map((r) => r.content) || [],
participant: participant
})
const responses = await Promise.all(
$groups.map((group) =>
fetch(`/api/session/${$page.params.id}/group/${group.id}/conversations`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
);
})
)
);
if (!response.ok) {
const data = await response.json();
notifications.error(
data.error || `無法為參與者 ${participantNames.get(participant)} 創建對話`
);
return;
}
}
// 檢查是否有任何請求失敗
const failedResponse = responses.find((response) => !response.ok);
if (failedResponse) {
const data = await failedResponse.json();
notifications.error(data.error || '無法為部分群組的參與者創建對話');
return;
}
// 更新 session 狀態
Expand Down
10 changes: 6 additions & 4 deletions src/lib/components/session/ParticipantView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
return unsbscribe;
});
$effect(() => {
if ($session?.status === 'before-group' && conversationDoc && !conversationDoc.data.summary) {
fetchSummary();
}
});
function updateConversationDoc() {
if (!groupDoc) {
conversationDoc = null;
Expand All @@ -79,10 +85,6 @@
data: snapshot.docs[0].data() as Conversation,
id: snapshot.docs[0].id
};
if ($session?.status === 'before-group' && !conversationDoc.data.summary) {
fetchSummary();
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/session/Summary.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
data: Conversation;
id: string;
};
export let loading: boolean;
export let loading = false;
export let onRefresh: () => Promise<void>;
export let readonly = false;
Expand Down
11 changes: 6 additions & 5 deletions src/lib/server/llm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { env } from '$env/dynamic/private';
import type { Resource } from '$lib/schema/resource';
import type { LLMChatMessage, StudentSpeak } from '$lib/utils/types';
import fs from 'fs/promises';
import { OpenAI } from 'openai';
Expand Down Expand Up @@ -134,10 +135,7 @@ export async function chatWithLLMByDocs(
history: LLMChatMessage[],
task: string,
subtasks: string[],
resources: {
name: string;
content: string;
}[],
resources: Resource[],
temperature = 0.7
): Promise<{ success: boolean; message: string; subtask_completed: boolean[]; error?: string }> {
console.log('Starting chatWithLLMByDocs:', {
Expand Down Expand Up @@ -242,7 +240,10 @@ export async function summarizeStudentChat(history: LLMChatMessage[]): Promise<{
}> {
console.log('Summarizing student chat:', { historyLength: history.length });
try {
const formatted_history = history.map((msg) => `${msg.role}: ${msg.content}`).join('\n');
const formatted_history = history
.filter((msg) => msg.role === 'user')
.map((msg) => msg.content)
.join('\n');
const system_prompt = CHAT_SUMMARY_PROMPT.replace('{chatHistory}', formatted_history);
const summary_student_opinion_schema = z.object({
student_summary: z.string(),
Expand Down
55 changes: 44 additions & 11 deletions src/lib/utils/firestore.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import type { Conversation } from '$lib/schema/conversation';
import type { Group } from '$lib/schema/group';
import type { Session } from '$lib/schema/session';
import { adminDb } from '$lib/server/firebase';
import { error } from '@sveltejs/kit';
import type { LLMChatMessage } from './types';

export async function createConversation(
id: string,
group_number: string,
userId: string,
task: string,
subtasks: string[],
resources: string[]
history: LLMChatMessage[],
resources: { name: string; content: string }[]
) {
const conversationsRef = adminDb
.collection('sessions')
Expand All @@ -29,7 +32,7 @@ export async function createConversation(
task: task,
subtasks: subtasks,
resources: resources,
history: [],
history: history,
subtaskCompleted: new Array(subtasks.length).fill(false)
});

Expand All @@ -45,6 +48,15 @@ export async function getConversationRef(id: string, group_number: string, conv_
.doc(conv_id);
}

export function getConversationsRef(id: string, group_number: string) {
return adminDb
.collection('sessions')
.doc(id)
.collection('groups')
.doc(group_number)
.collection('conversations');
}

export async function getConversationData(
conversation_ref: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>
): Promise<Conversation> {
Expand All @@ -56,15 +68,6 @@ export async function getConversationData(
return conversation.data() as Conversation;
}

export function getConversationsRef(id: string, group_number: string) {
return adminDb
.collection('sessions')
.doc(id)
.collection('groups')
.doc(group_number)
.collection('conversations');
}

export async function getConversationsData(
conversations_ref: FirebaseFirestore.CollectionReference<
FirebaseFirestore.DocumentData,
Expand All @@ -83,6 +86,10 @@ export function getGroupRef(id: string, group_number: string) {
return adminDb.collection('sessions').doc(id).collection('groups').doc(group_number);
}

export function getGroupsRef(id: string) {
return adminDb.collection('sessions').doc(id).collection('groups');
}

export async function getGroupData(
group_ref: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>
): Promise<Group> {
Expand All @@ -93,3 +100,29 @@ export async function getGroupData(

return group.data() as Group;
}

export async function getGroupsData(
groups_ref: FirebaseFirestore.CollectionReference<FirebaseFirestore.DocumentData>
): Promise<Group[]> {
const groups = await groups_ref.get();
if (groups.empty) {
throw error(404, 'Groups not found');
}

return groups.docs.map((doc) => doc.data() as Group);
}

export function getSessionRef(id: string) {
return adminDb.collection('sessions').doc(id);
}

export async function getSessionData(
session_ref: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>
): Promise<Session> {
const session = await session_ref.get();
if (!session.exists) {
throw error(404, 'Session not found');
}

return session.data() as Session;
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { createConversation } from '$lib/utils/firestore';
import { chatWithLLMByDocs } from '$lib/server/llm';
import {
createConversation,
getGroupData,
getGroupRef,
getSessionData,
getSessionRef
} from '$lib/utils/firestore';
import type { LLMChatMessage } from '$lib/utils/types';
import type { RequestHandler } from '@sveltejs/kit';
import { error, json, redirect } from '@sveltejs/kit';
import { z } from 'zod';

// 更新請求數據格式
const requestDataFormat = z.object({
task: z.string(),
subtasks: z.array(z.string()),
resources: z.array(z.string()),
participant: z.string()
});

export const POST: RequestHandler = async ({ request, params, locals }) => {
export const GET: RequestHandler = async ({ params, locals }) => {
try {
if (!locals.user) {
redirect(303, '/login');
Expand All @@ -21,37 +20,28 @@ export const POST: RequestHandler = async ({ request, params, locals }) => {
throw error(400, 'Missing parameters');
}

const { task, subtasks, resources, participant } = await getRequestData(request);
const session_ref = getSessionRef(id);
const { task, subtasks, resources } = await getSessionData(session_ref);
const group_ref = getGroupRef(id, group_number);
const group_data = await getGroupData(group_ref);

const intro = await chatWithLLMByDocs([], task, subtasks, resources);
if (!intro) {
throw error(500, 'Error generating intro message');
}
const history: LLMChatMessage[] = [{ role: 'assistant', content: intro.message }];

const conv_id = await createConversation(
id,
group_number,
participant,
task,
subtasks,
resources
await Promise.all(
group_data.participants.map(async (participant) =>
createConversation(id, group_number, participant, task, subtasks, history, resources)
)
);

return json({
success: true,
conversationId: conv_id
success: true
});
} catch (error) {
console.error('Error creating conversation:', error);
return json({ error: 'Internal Server Error' }, { status: 500 });
}
};

async function getRequestData(request: Request): Promise<z.infer<typeof requestDataFormat>> {
const data = await request.json();
const result = requestDataFormat.parse(data);
if (!result.task || !result.subtasks || !result.resources || !result.participant) {
throw error(
400,
`Missing parameters: ${!result.task ? 'task ' : ''}${!result.subtasks ? 'subtasks ' : ''}${
!result.resources ? 'resources ' : ''
}${!result.participant ? 'participant' : ''}`.trim()
);
}
return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const POST: RequestHandler = async ({ request, params, locals }) => {

const conversation_ref = await getConversationRef(id, group_number, conv_id);
console.log('Retrieved conversation reference');
const { userId, task, subtasks, resources, history } =
const { userId, task, subtasks, resources, history, subtaskCompleted } =
await getConversationData(conversation_ref);
console.log('Retrieved conversation data', { userId, task, subtasksCount: subtasks.length });

Expand Down Expand Up @@ -83,7 +83,9 @@ export const POST: RequestHandler = async ({ request, params, locals }) => {
content: response.message
}
],
subtaskCompleted: response.subtask_completed
subtaskCompleted: subtaskCompleted.map(
(completed, index) => completed || response.subtask_completed[index]
)
});

return json({ success: true, message: response.message });
Expand Down

0 comments on commit e933daa

Please sign in to comment.