diff --git a/functions/handlers.py b/functions/handlers.py index 7a27fd0..20271fd 100644 --- a/functions/handlers.py +++ b/functions/handlers.py @@ -2,6 +2,8 @@ from chain import create_health_ai_chain from config import initialize_firebase +from langchain_anthropic import ChatAnthropic +from langchain_google_genai import ChatGoogleGenerativeAI from langchain_openai import ChatOpenAI from store import get_vector_store @@ -16,10 +18,19 @@ def get_health_ai_response(question, llm): def get_response_from_llm(query, llm): - models = {'gpt-4': {}, 'gpt-3.5-turbo-instruct': {'name': 'gpt-3.5-turbo-instruct'}} - if llm in models: - llm_model = ChatOpenAI(api_key=environ.get('OPEN_AI_API_KEY'), temperature=0, **models[llm]) + if llm == 'gpt-4' or llm == 'gpt-3.5-turbo-instruct': + llm_model = ChatOpenAI(api_key=environ.get('OPEN_AI_API_KEY'), temperature=0, model=llm) + response = get_health_ai_response(query, llm_model) + elif llm == 'google': + llm_model = ChatGoogleGenerativeAI( + model='gemini-1.5-pro-latest', google_api_key=environ.get('GOOGLE_API_KEY') + ) + response = get_health_ai_response(query, llm_model) + elif llm == 'claude': + llm_model = ChatAnthropic( + model='claude-3-5-sonnet-20240620', api_key=environ.get('ANTHROPIC_API_KEY') + ) response = get_health_ai_response(query, llm_model) - return response else: - return 'Model Not Found' + response = 'Model Not Found' + return response diff --git a/functions/main.py b/functions/main.py index 5364366..5796471 100644 --- a/functions/main.py +++ b/functions/main.py @@ -4,7 +4,7 @@ from handlers import get_response_from_llm -@https_fn.on_request(cors=options.CorsOptions(cors_origins=['*'])) +@https_fn.on_request(memory=options.MemoryOption.GB_32, cpu=8, timeout_sec=300) def get_response_url(req: https_fn.Request) -> https_fn.Response: query = req.get_json().get('query', '') llms = req.get_json().get('llms', ['gpt-4']) @@ -15,7 +15,7 @@ def get_response_url(req: https_fn.Request) -> https_fn.Response: return https_fn.Response(dumps(responses), mimetype='application/json') -@https_fn.on_call() +@https_fn.on_call(memory=options.MemoryOption.GB_32, cpu=8, timeout_sec=300) def get_response(req: https_fn.CallableRequest): query = req.data.get('query', '') llms = req.data.get('llms', ['gpt-4']) diff --git a/functions/requirements.txt b/functions/requirements.txt index 734752b..0fd90e7 100644 --- a/functions/requirements.txt +++ b/functions/requirements.txt @@ -4,3 +4,10 @@ langchain-community langchain-openai langchain-astradb lark +langchain_core +langchain_google_genai +langchain_anthropic +google-auth +google-auth-oauthlib +google-api-python-client +python-dotenv diff --git a/src/backend/Scrapers/YouTube/you_tube.py b/src/backend/Scrapers/YouTube/you_tube.py index 6e3b2be..c123875 100644 --- a/src/backend/Scrapers/YouTube/you_tube.py +++ b/src/backend/Scrapers/YouTube/you_tube.py @@ -1,5 +1,6 @@ import html import json +import logging import os import re from typing import List @@ -14,9 +15,6 @@ from src.backend.Scrapers.YouTube import INDEX_FILE_PATH, RAW_DIR_PATH from src.backend.Types.you_tube import TypeYouTubeScrappingData from src.backend.Utils.splitter import get_text_chunks -import logging - - class YouTubeScraper(BaseScraper): @@ -100,7 +98,7 @@ def _scrape(self) -> TypeYouTubeScrappingData: scrap_data['ref'] = f'https://www.youtube.com/watch?v={scrap_data["videoId"]}' return scrap_data except Exception as e: - #print(f'Error: {e} No caption found for videoId: {self.element_id}') + # print(f'Error: {e} No caption found for videoId: {self.element_id}') error_msg = f'Error: {e} No caption found for videoId: {self.element_id}' write_to_log(self.element_id, self.__class__.__name__, error_msg) return {} diff --git a/src/frontend/components/DropdownMenu/index.tsx b/src/frontend/components/DropdownMenu/index.tsx index 8e93e86..a9f333f 100644 --- a/src/frontend/components/DropdownMenu/index.tsx +++ b/src/frontend/components/DropdownMenu/index.tsx @@ -18,11 +18,11 @@ import { Style } from './style'; */ export const DropdownMenu = () => { + const { params } = useRoute>(); const [isVisible, setIsVisible] = useState(false); - const { activeChatId, setActiveChatId } = useActiveChatId(); - const { activeLLMs, toggleLLM } = useLLMs(activeChatId || 'default'); + const { activeLLMs, toggleLLM } = useLLMs(params?.chatId || 'new'); - const { chat, status, error } = useGetChat(activeChatId); + const { chat, status } = useGetChat(params?.chatId || 'new'); const activeLLMsCount = Object.values(activeLLMs).filter((llm) => llm.active).length; const activeLLMsNames = Object.values(activeLLMs) diff --git a/src/frontend/components/RenderChat/index.tsx b/src/frontend/components/RenderChat/index.tsx index cf0bc02..ee55441 100644 --- a/src/frontend/components/RenderChat/index.tsx +++ b/src/frontend/components/RenderChat/index.tsx @@ -1,12 +1,7 @@ -import React, { useEffect } from 'react'; -import { Text, View } from 'react-native'; -import Markdown from 'react-native-markdown-display'; -import { ActivityIndicator, Avatar, useTheme } from 'react-native-paper'; -import { useUser } from 'reactfire'; -import { useActiveChatId, useGetChat } from 'src/frontend/hooks'; +import { ActivityIndicator } from 'react-native-paper'; +import { useGetChat } from 'src/frontend/hooks'; import type { conversationMessage } from 'src/frontend/types'; import { ChatBubble } from '../ChatBubble'; -import { Style } from './style'; /** * This file handles rendering all the different chat bubbles from a saved chat in firestore @@ -14,12 +9,13 @@ import { Style } from './style'; * There is case distinction between AI and user messages because the storage format is different */ -export function RenderChat() { - const { activeChatId } = useActiveChatId(); - const { chat, status } = useGetChat(activeChatId); - const { colors } = useTheme(); - const { data: user } = useUser(); +type RenderChatProps = { + chatId: string; +}; +export function RenderChat(props: RenderChatProps) { + const { chatId } = props; + const { chat, status } = useGetChat(chatId); if (status === 'loading') return ; let id = 0; return ( diff --git a/src/frontend/hooks/useUpdateChat.ts b/src/frontend/hooks/useUpdateChat.ts index 7400f28..0f6f3d7 100644 --- a/src/frontend/hooks/useUpdateChat.ts +++ b/src/frontend/hooks/useUpdateChat.ts @@ -10,14 +10,14 @@ import type { Chat } from '../types'; * E.g. with updated conversations with an LLM */ -export function useUpdateChat(chatId: string) { +export function useUpdateChat() { const [isUpdating, setIsUpdating] = useState(false); const [error, setError] = useState(null); const [isSuccess, setIsSuccess] = useState(false); const { data: users } = useUser(); const firestore = useFirestore(); - const updateChat = async (data: Partial) => { + const updateChat = async (chatId: string, data: Partial) => { setIsUpdating(true); setIsSuccess(false); try { diff --git a/src/frontend/routes/MainRoutes.tsx b/src/frontend/routes/MainRoutes.tsx index 530d473..885548a 100644 --- a/src/frontend/routes/MainRoutes.tsx +++ b/src/frontend/routes/MainRoutes.tsx @@ -2,7 +2,7 @@ import { createDrawerNavigator } from '@react-navigation/drawer'; import React from 'react'; import { Header } from '../components'; import { Screens } from '../helpers'; -import { ChatUI, CustomInstructions, DrawerMenu } from '../screens'; +import { Chat, ChatUI, CustomInstructions, DrawerMenu } from '../screens'; export type MainDrawerParams = { [Screens.Chat]: { chatId: string | null }; @@ -16,7 +16,7 @@ export function MainRoutes() { }>
}} /> >(); + const { setParams } = useNavigation>(); + const { colors } = useTheme(); + const [activeChatId, setActiveChatId] = useState('new'); + const { chat } = useGetChat(activeChatId); + const { updateChat } = useUpdateChat(); + const { createChat } = useCreateChat(); + const scrollViewRef = useRef(null); + const [text, setText] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const getResponse = useGetResponse(); + + // ------------- Keyboard scrolls down when sending a message ------------- + useEffect(() => { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }, [chat?.conversation.length]); + + // ------------- Create chat ------------- + const createNewChat = async (firstMessage: string) => { + let chatId = 'new'; + try { + const userPrompt = { type: 'USER', message: firstMessage }; + const result = await createChat({ + title: firstMessage, + model: ['gpt-4'], + conversation: [userPrompt] + }); + setActiveChatId(result?.id || 'new'); + setParams({ chatId: result?.id }); + chatId = result?.id || 'new'; + } catch (error) { + console.error('Error creating chat:', error); + } + return chatId; + }; + + // ------------- Update chat id ------------- + useEffect(() => { + setActiveChatId(params?.chatId || 'new'); + }, [params?.chatId]); + + // biome-ignore lint/suspicious/noExplicitAny: + async function fetchResponse(query: string): Promise { + const url = 'https://us-central1-amos-agent-framework.cloudfunctions.net/get_response_url_2'; + const data = { + query: query, + llms: chat?.model || ['gpt-4'], + history: JSON.parse(JSON.stringify(chat?.conversation || [])) + }; + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + const responseData = await response.json(); + return responseData; + } catch (error) { + console.error('Error:', error); + throw error; + } + } + + // ------------- Send Message ------------- + const sendMessage = async () => { + const queryText = text.trim(); + try { + setIsLoading(true); + setText(''); + if (!queryText) return; + if (activeChatId === 'new') { + const chatId = await createNewChat(text); + const data = await fetchResponse(queryText); + await updateChat(chatId, { conversation: arrayUnion({ type: 'AI', message: data }) }); + } else { + await updateChat(activeChatId, { + conversation: arrayUnion({ type: 'USER', message: queryText }) + }); + const data = await fetchResponse(queryText); + await updateChat(activeChatId, { conversation: arrayUnion({ type: 'AI', message: data }) }); + } + } catch (error) { + console.error(JSON.stringify(error)); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + {isLoading && } + + + + + + + ); +} diff --git a/src/frontend/screens/ChatUI/style.ts b/src/frontend/screens/Chat/style.ts similarity index 100% rename from src/frontend/screens/ChatUI/style.ts rename to src/frontend/screens/Chat/style.ts diff --git a/src/frontend/screens/ChatUI/index.tsx b/src/frontend/screens/ChatUI/index.tsx deleted file mode 100644 index 469b19d..0000000 --- a/src/frontend/screens/ChatUI/index.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import type { FirebaseError } from 'firebase/app'; -import { arrayUnion } from 'firebase/firestore'; -import React from 'react'; -import { useState } from 'react'; -import { useEffect, useRef } from 'react'; -import { ScrollView, View } from 'react-native'; -import { ActivityIndicator } from 'react-native-paper'; -import { useTheme } from 'react-native-paper'; -import { ChatKeyboard, RenderChat, VoiceButton } from 'src/frontend/components'; -import { - LLM_MODELS, - useActiveChatId, - useCreateChat, - useGetChat, - useGetResponse, - useLLMs, - useUpdateChat -} from 'src/frontend/hooks'; -import { styles } from './style'; - -/** - * This file renders the chat UI in the main screen. - * - * Current Active Chat is given by @activeChatId and retrieved from the firestore DB - * If a user sends a message, the chat is updated with the new message - * and we need to get an LLM response for every selected LLM in the dropdown - * Contains Error handling for LLM response retrieval - */ - -export type ChatUiProps = { - chatId: string; -}; - -export function ChatUI() { - const { colors } = useTheme(); - const scrollViewRef = useRef(null); // Scrolling down chat when message is sent - const { activeChatId, setActiveChatId } = useActiveChatId(); // chat id of current chat - const { chat } = useGetChat(activeChatId); // current displayed chat - const [text, setText] = useState(''); //text in the input field - const [isSendButtonDisabled, setSendButtonDisabled] = useState(false); - const getResponse = useGetResponse(); // LLM firebase function - const { updateChat } = useUpdateChat(activeChatId); // update chat in firestore - const { createChat } = useCreateChat(); - const { activeLLMs } = useLLMs(activeChatId || 'default'); - // Flag to wait for LLM answer when a new chat is created - const [waitingForAnswerOnNewChat, setWaitingForAnswerOnNewChat] = useState<{ - waiting: boolean; - query: string; - newChatId: string; - }>({ waiting: false, query: '', newChatId: 'default' }); - - // ------------- Keyboard scrolls down when sending a message ------------- - useEffect(() => { - scrollViewRef.current?.scrollToEnd({ animated: true }); - }, [chat?.conversation.length]); - - //---------------- Sending messages and managing Firestore ---------------- - - // This function is called when the user sends a prompt - async function sendMessage() { - setSendButtonDisabled(true); - const query = text.trim(); - setText(''); - - // If chat is 'default' (empty), create a new chat when prompt is sent - // Otherwise just update chat with LLM answer - - // Create new chat - if (activeChatId === 'default') { - const newId = await createNewChat(query); - if (newId !== undefined && newId !== '') { - // once all chat variables are updated, get the LLM answer in async effect @fetchLLMAnswer - setWaitingForAnswerOnNewChat({ waiting: true, query: query, newChatId: newId }); - } - - // Update existing chat - } else { - try { - await updateChat({ conversation: arrayUnion({ type: 'USER', message: query }) }); - await getLLMAnswer(query); - } catch (error) { - console.error('Error in sendMessage:', error); - } finally { - setSendButtonDisabled(false); - } - } - } - - // ---- We want to query the LLM with the chat prompt when a new chat is created ---- - // Need to wait for @chat and @activeChatId to be updated - // @WaitingForAnswerOnNewChat flag is set in sendMessage() when a new chat is created - useEffect(() => { - const fetchLLMAnswer = async () => { - // this condition is probably not totally correct - if ( - waitingForAnswerOnNewChat.waiting && - activeChatId !== 'default' && - activeChatId === waitingForAnswerOnNewChat.newChatId - ) { - try { - await getLLMAnswer(waitingForAnswerOnNewChat.query); - } catch (error) { - console.error('Error getting LLM answer:', error); - } finally { - // once the LLM answer is retrieved, set the flag to false - setWaitingForAnswerOnNewChat({ - waiting: false, - query: '', - newChatId: waitingForAnswerOnNewChat.newChatId - }); - } - } - }; - fetchLLMAnswer(); - }, [waitingForAnswerOnNewChat.waiting, activeChatId]); - - // Create new chat and update props of this component - const createNewChat = async (queryText: string) => { - const title = extractTitle(queryText); - const userPrompt = { type: 'USER', message: queryText }; // correct firestore format - try { - const result = await createChat({ - title: title, - model: [LLM_MODELS[0].key as string], - conversation: [userPrompt] - }); - // Make set active async and wait for reply - const id = result?.id; - setActiveChatId(id || ''); - - return result?.id; - } catch (error) { - console.error(error); - } - return ''; - }; - - async function getLLMAnswer(queryText: string) { - // default response - - let response: { [key: string]: string } = initResponses(); - - // get Response from LLM - try { - //get active LLMS in correct format - const llms = extractActiveLLMNames(); - const { data } = await getResponse({ query: queryText, llms: llms }); - response = data as { [key: string]: string }; - } catch (error) { - console.error(error as FirebaseError); - } - - // Update chat with LLM response - try { - await updateChat({ conversation: arrayUnion({ type: 'AI', message: response }) }); - } catch (error) { - console.error(error as FirebaseError); - } finally { - setSendButtonDisabled(false); // Once all is updated, the user is allowed to type again - } - } - - // ------------- Helper functions ------------- - // get default responses for all active LLMs if response retrieval fails - function initResponses() { - const response: { [key: string]: string } = Object.keys(activeLLMs).reduce( - (acc, key) => { - if (activeLLMs[key].active) { - acc[key] = 'Could not retrieve answer from LLM'; - } - return acc; - }, - {} as { [key: string]: string } - ); - if (Object.keys(response).length === 0) - response['gpt-4'] = 'Could not retrieve answer from LLM'; - return response; - } - - // returns currently active selected LLMs in dropdown and 'gpt-4' if no LLM is selected - function extractActiveLLMNames() { - const llms: string[] = []; - for (const llm of Object.keys(activeLLMs)) { - if (activeLLMs[llm].active) { - llms.push(llm); - } - } - - if (llms.length === 0) llms.push('gpt-4'); - return llms; - } - - // Extract title to be displayed for a chat in the drawer menu - function extractTitle(queryText: string) { - //TODO: maybe use a more sophisticated method to extract the title later - const arr = queryText.split(' '); - let title = ''; - for (let i = 0; i < arr.length && i < 5; i++) { - title += `${arr[i]} `; - } - return title; - } - // ------------- Render ------------- - - return ( - - - - {isSendButtonDisabled && } - - - - - - - ); -} diff --git a/src/frontend/screens/DrawerMenu/index.tsx b/src/frontend/screens/DrawerMenu/index.tsx index 6785a31..d58f79e 100644 --- a/src/frontend/screens/DrawerMenu/index.tsx +++ b/src/frontend/screens/DrawerMenu/index.tsx @@ -47,7 +47,7 @@ export function DrawerMenu() { const { setActiveChatId } = useActiveChatId(); const createNewChat = () => { setActiveChatId('default'); - navigate('Main', { screen: Screens.Chat, params: { chatId: 'default' } }); // only used to close drawer + navigate('Main', { screen: Screens.Chat, params: { chatId: null } }); // only used to close drawer }; const handleLogout = async () => { diff --git a/src/frontend/screens/index.ts b/src/frontend/screens/index.ts index a46524a..ac0dec9 100644 --- a/src/frontend/screens/index.ts +++ b/src/frontend/screens/index.ts @@ -1,9 +1,9 @@ export * from './Fallback'; export * from './Landing'; export * from './Login'; -export * from './ChatUI'; export * from './DrawerMenu'; export * from './SignUp'; export * from './ForgotPassword'; export * from './ResetPassword'; export * from './CustomInstructions'; +export * from './Chat';