diff --git a/app/client/api.ts b/app/client/api.ts index 5c63f57..7b7b669 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -1,6 +1,8 @@ import { getClientConfig } from "../config/client"; import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant"; import { ChatMessage, ModelType, useAccessStore } from "../store"; +import { KGConfig } from "../store/kg"; +import { RAGConfig } from "../store/rag"; import { ChatGPTApi } from "./platforms/openai"; export const ROLES = ["system", "user", "assistant"] as const; @@ -23,9 +25,20 @@ export interface LLMConfig { frequency_penalty?: number; } +export interface AgentInfo { + useRAG: boolean; + useKG: boolean; + useOncoKB: boolean; + useAutoAgent: boolean; + kgConfig?: KGConfig; + ragConfig?: RAGConfig; + oncokbConfig?: Record; +} + export interface ChatOptions { messages: RequestMessage[]; config: LLMConfig; + agentInfo?: AgentInfo; onUpdate?: (message: string, chunk: string) => void; onFinish: (message: string, context?: any[]) => void; diff --git a/app/client/datarequest.ts b/app/client/datarequest.ts index 4d65e1f..00048c2 100644 --- a/app/client/datarequest.ts +++ b/app/client/datarequest.ts @@ -35,7 +35,7 @@ export const requestKGConnectionStatus = async ( export const requestAllVSDocuments = async ( connectionArgs: DbConnectionArgs, - docIds: string[] + docIds?: string[] ) => { const RAG_URL = ApiPath.RAG; let fetchUrl = RAG_URL as string; diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 77c9c13..221e1d2 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -19,6 +19,7 @@ import { getClientConfig } from "@/app/config/client"; import { makeAzurePath } from "@/app/azure"; import { useRAGStore } from "@/app/store/rag"; import { useKGStore } from "@/app/store/kg"; +import { getKnowledgeGraphInfo, getOncoKBInfo, getVectorStoreInfo } from "@/app/utils/prodinfo"; export interface OpenAIListModelResponse { object: string; @@ -81,11 +82,7 @@ export class ChatGPTApi implements LLMApi { model: options.config.model, }, }; - const ragConfig = useRAGStore.getState().currentRAGConfig(); - const useRAG = useChatStore.getState().currentSession().useRAGSession; - const useKG = useChatStore.getState().currentSession().useKGSession; - const kgConfig = useKGStore.getState().config; - + const requestPayload = { messages, stream: options.config.stream, @@ -97,10 +94,12 @@ export class ChatGPTApi implements LLMApi { // max_tokens: Math.max(modelConfig.max_tokens, 1024), // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore. session_id: useChatStore.getState().currentSession().id, - useRAG, - useKG, - ragConfig, - kgConfig, + useRAG: options.agentInfo?.useRAG??false, + useKG: options.agentInfo?.useKG??false, + ragConfig: options.agentInfo?.ragConfig, + kgConfig: options.agentInfo?.kgConfig, + oncokbConfig: options.agentInfo?.oncokbConfig, + useAutoAgent: options.agentInfo?.useAutoAgent??false, }; console.log("[Request] openai payload: ", requestPayload); diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index a2c09bd..94ac34e 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -67,6 +67,10 @@ flex-direction: row; margin-right: 10px; + .agent-checkbox:disabled { + cursor: not-allowed; + } + &:last-child { margin-right: 20px; } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 72785b9..02dd49a 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -28,13 +28,9 @@ import EditIcon from "../icons/rename.svg"; import ConfirmIcon from "../icons/confirm.svg"; import CancelIcon from "../icons/cancel.svg"; -import LightIcon from "../icons/light.svg"; -import DarkIcon from "../icons/dark.svg"; -import AutoIcon from "../icons/auto.svg"; import BottomIcon from "../icons/bottom.svg"; import StopIcon from "../icons/pause.svg"; import RobotIcon from "../icons/robot.svg"; -import RagIcon from "../icons/rag.svg"; import { ChatMessage, @@ -94,6 +90,7 @@ import { useAllModels } from "../utils/hooks"; import { useRAGStore } from "../store/rag"; import { useKGStore } from "../store/kg"; import { DbConfiguration } from "../utils/datatypes"; +import { getOncoKBInfo } from "../utils/prodinfo"; const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { loading: () => , @@ -174,7 +171,7 @@ function PromptToast(props: { const chatStore = useChatStore(); const session = chatStore.currentSession(); const context = session.mask.context; - + return (
{props.showToast && ( @@ -197,7 +194,7 @@ function PromptToast(props: { } function RagPromptToast( - {showModal, setShowModal}: {showModal: boolean, setShowModal: (_: boolean) => void} + { showModal, setShowModal }: { showModal: boolean, setShowModal: (_: boolean) => void } ) { const chatStore = useChatStore(); const session = chatStore.currentSession(); @@ -441,14 +438,18 @@ export function ChatActions(props: { const chatStore = useChatStore(); const accessStore = useAccessStore(); const prodInfo = accessStore.productionInfo === "undefined" ? undefined : JSON.parse(accessStore.productionInfo); - const kgProdInfo = (prodInfo?.KnowledgeGraph ?? {servers: [], enabled: true}) as DbConfiguration; - const ragProdInfo = (prodInfo?.VectorStore ?? {servers: [], enabled: true}) as DbConfiguration; + const kgProdInfo = (prodInfo?.KnowledgeGraph ?? { servers: [], enabled: true }) as DbConfiguration; + const ragProdInfo = (prodInfo?.VectorStore ?? { servers: [], enabled: true }) as DbConfiguration; + const oncokbInfo = getOncoKBInfo(prodInfo); + const agentEnableFlags = [kgProdInfo.enabled, ragProdInfo.enabled, oncokbInfo.enabled] + let enabledAgentsNum = 0; + agentEnableFlags.forEach((flag) => (enabledAgentsNum += flag ? 1 : 0)); const session = chatStore.currentSession(); const contexts = session.contextualPrompts; const total_rag_prompts_num = contexts.reduce((prev, cur) => (prev + cur.context.length), 0); - const rag_prompts_text = (total_rag_prompts_num === 0) ? + const rag_prompts_text = (total_rag_prompts_num === 0) ? (Locale.RagContext.Toast("0", "")) : - (contexts.map((ctx) => (ctx.context.length > 0 ? + (contexts.map((ctx) => (ctx.context.length > 0 ? Locale.RagContext.Toast(ctx.context.length, ctx.mode) : "" ))).join(" "); @@ -566,16 +567,45 @@ export function ChatActions(props: { )}
+ {oncokbInfo.enabled && ( +
+ + ( + chatStore.updateCurrentSession( + (session) => { + session.useOncoKBSession = e.currentTarget.checked; + if (session.useOncoKBSession) { + session.useKGSession = false; + session.useRAGSession = false; + } + } + ) + )} + /> +
+ )} {ragProdInfo.enabled && (
- + ( chatStore.updateCurrentSession( - (session) => (session.useRAGSession = e.currentTarget.checked) + (session) => { + session.useRAGSession = e.currentTarget.checked; + if (session.useRAGSession) { + session.useKGSession = false; + session.useOncoKBSession = false; + } + } ) )} /> @@ -583,14 +613,21 @@ export function ChatActions(props: { )} {kgProdInfo.enabled && (
- + ( chatStore.updateCurrentSession( - (session) => (session.useKGSession = e.currentTarget.checked) + (session) => { + session.useKGSession = e.currentTarget.checked; + if (session.useKGSession) { + session.useRAGSession = false; + session.useOncoKBSession = false; + } + } ) )} /> @@ -923,6 +960,22 @@ function _Chat() { }); }; + const getLoadingText = ( + useOncoKB: boolean, + useRAG: boolean, + useKG: boolean, + ): string | undefined => { + if (useOncoKB) { + return Locale.Chat.Loading.OncoKB; + } else if (useRAG) { + return Locale.Chat.Loading.RAG; + } else if (useKG) { + return Locale.Chat.Loading.KG; + } else { + return undefined; + } + } + const context: RenderMessage[] = useMemo(() => { return session.mask.hideContext ? [] : session.mask.context.slice(); }, [session.mask.context, session.mask.hideContext]); @@ -1298,6 +1351,13 @@ function _Chat() { message.content.length === 0 && !isUser } + loadingText={ + ((message.preview || message.streaming) && + message.content.length === 0 && + !isUser) ? + (getLoadingText(session.useOncoKBSession, session.useRAGSession, session.useKGSession)) : + undefined + } onContextMenu={(e) => onRightClick(e, message)} onDoubleClickCapture={() => { if (!isMobileScreen) return; diff --git a/app/components/kg.tsx b/app/components/kg.tsx index 8b65ac3..d2163da 100644 --- a/app/components/kg.tsx +++ b/app/components/kg.tsx @@ -23,7 +23,7 @@ import { List, ListItem, SelectInput } from "./ui-lib"; import { InputRange } from "./input-range"; import { DbConnectionArgs } from "../utils/datatypes"; -import { getConnectionArgsToConnect, getConnectionArgsToDisplay } from "../utils/rag"; +import { getKGConnectionArgsToConnect, getKGConnectionArgsToDisplay } from "../utils/rag"; const DEFAULT_PORT = "7687"; const DEFAULT_HOST = ""; @@ -36,7 +36,7 @@ export function KGPage() { let kgProdInfo = (prodInfo?.KnowledgeGraph ?? {servers: []}) as DbConfiguration; const kgConfig = kgStore.config; const [connectionArgs, setConnectionArgs] - = useState(getConnectionArgsToDisplay(kgConfig.connectionArgs, kgProdInfo.servers ?? [])); + = useState(getKGConnectionArgsToDisplay(kgConfig.connectionArgs, kgProdInfo.servers ?? [])); const [uploading, setUploading] = useState(false); const [document, setDocument] = useState(); const [connected, setConnected] = useState(false); @@ -65,7 +65,7 @@ export function KGPage() { const updateConnectionStatus = useDebouncedCallback(async () => { setIsReconnecting(true); try { - const conn = getConnectionArgsToConnect(connectionArgs, kgProdInfo.servers??[]); + const conn = getKGConnectionArgsToConnect(connectionArgs, kgProdInfo.servers??[]); const res = await requestKGConnectionStatus(conn); const value = await res.json(); if(value?.code === ERROR_BIOSERVER_OK && value.status) { @@ -205,6 +205,9 @@ export function KGPage() { if (kg.number_of_results !== undefined) { config.resultNum = kg.number_of_results; } + if (kg.description !== undefined) { + config.description = kg.description; + } } ) break; @@ -283,6 +286,20 @@ export function KGPage() { }} > + + { + kgStore.updateConfig( + (config) => (config.useReflexion = e.currentTarget?.checked??false) + ) + }} + > +
diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index f3a916c..1d26118 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -157,6 +157,7 @@ export function Markdown( props: { content: string; loading?: boolean; + loadingText?: string; fontSize?: number; parentRef?: RefObject; defaultShow?: boolean; @@ -176,7 +177,10 @@ export function Markdown( dir="auto" > {props.loading ? ( - + props.loadingText ? ( +
+

{props.loadingText}

+
) : () ) : ( )} diff --git a/app/components/rag-contextual-prompts-modal.tsx b/app/components/rag-contextual-prompts-modal.tsx index 216c9be..6f5c702 100644 --- a/app/components/rag-contextual-prompts-modal.tsx +++ b/app/components/rag-contextual-prompts-modal.tsx @@ -10,6 +10,7 @@ export function RagContextualPromptsModal( contexts: Array, } ) { + return (
( ctx.context.length === 0 ? (<>) : (
{`${Locale.RagContext.ModeType}: ${ctx.mode}`} - {ctx.context.map(ctxItem => ( - (ctx.mode === "kg") ? ( - <> + {ctx.context.map(ctxItem => { + if (ctx.mode === "kg") { + return (<>
{ctxItem[0]}
{`cypher_query: ${ctxItem[1].cypher_query}`}
- - ) : ( - <> + ); + } else if (ctx.mode === "vectorstore") { + return (<>
{ctxItem[0]}
{` source: ${ctxItem[1].source}`}
- - ) - ))} + ); + } else if (ctx.mode === "api_oncokb") { + const source = ctxItem[1] === "error" ? "error information" : "source: OncoKB"; + return (<> +
{ctxItem[0]}
+
{` ${source}`}
+ ); + } else { + return (<>); + } + })}
) ))} diff --git a/app/components/rag.tsx b/app/components/rag.tsx index 32c027f..35bafbc 100644 --- a/app/components/rag.tsx +++ b/app/components/rag.tsx @@ -22,7 +22,7 @@ import { InputRange } from "./input-range"; import { useDebouncedCallback } from "use-debounce"; import { requestAllVSDocuments, requestRemoveDocument, requestUploadFile, requestVSConnectionStatus } from "../client/datarequest"; import { DbConfiguration } from "../utils/datatypes"; -import { getConnectionArgsToConnect, getConnectionArgsToDisplay } from "../utils/rag"; +import { getVectorStoreConnectionArgsToConnect, getVectorStoreConnectionArgsToDisplay, getVectorStoreServerGlobal } from "../utils/rag"; const DEFAULT_PORT = "19530"; const DEFAULT_HOST = ""; @@ -55,17 +55,20 @@ export function RAGPage() { const [isReconnecting, setIsReconnecting] = useState(false); const ragConfig = ragStore.currentRAGConfig(); const [connectionArgs, setConnectionArgs] = useState( - getConnectionArgsToDisplay(ragConfig.connectionArgs, ragProdInfo.servers??[]) + getVectorStoreConnectionArgsToDisplay(ragConfig.connectionArgs, ragProdInfo.servers??[]) + ); + const [globalVS, setGlobalVS] = useState( + getVectorStoreServerGlobal(ragConfig.connectionArgs, ragProdInfo.servers??[]) ); const updateDocuments = useDebouncedCallback(async () => { const theConfig = ragStore.currentRAGConfig(); console.log(`[updateDocuments] ${theConfig.connectionArgs.host}`); console.log(`[updateDocuments] ${ragConfig.connectionArgs.host}`); - try { const res = await requestAllVSDocuments( - theConfig.connectionArgs, theConfig.docIdsWorkspace + getVectorStoreConnectionArgsToConnect(connectionArgs, ragProdInfo.servers??[]), + globalVS ? undefined : theConfig.docIdsWorkspace, ); const value = await res.json(); if (value.documents) { @@ -88,15 +91,18 @@ export function RAGPage() { } const connectionStatusUrl = fetchUrl + "connectionstatus"; setIsReconnecting(true); + const connectionArgsAddress = getVectorStoreConnectionArgsToConnect( + connectionArgs, ragProdInfo.servers??[] + ); + const theGlobalVS = getVectorStoreServerGlobal(connectionArgs, ragProdInfo.servers??[]); + setGlobalVS(theGlobalVS); try { - const res = await requestVSConnectionStatus( - getConnectionArgsToConnect(connectionArgs, ragProdInfo.servers??[] - )); + const res = await requestVSConnectionStatus(connectionArgsAddress); const value = await res.json(); if (value?.code === ERROR_BIOSERVER_OK && value.status) { if (value.status === "connected") { setConnected(true); - ragStore.selectRAGConfig(connectionArgs); + ragStore.selectRAGConfig(connectionArgsAddress); } else { setConnected(false); } @@ -138,18 +144,20 @@ export function RAGPage() { updateDocuments(); } } - async function onRemoveDocument(docId: string) { + async function onRemoveDocument(docId: string, docName: string) { if (docId.length === 0) { return; } - if (!await showConfirm(`Are you sure to remove the document?`)) { + if (!await showConfirm(`Are you sure to remove the document ${docName}?`)) { return; } try { const res = await requestRemoveDocument( ragConfig.connectionArgs, docId, - ragConfig.docIdsWorkspace + (ragConfig.docIdsWorkspace !== undefined && + ragConfig.docIdsWorkspace.length > 0) ? + ragConfig.docIdsWorkspace : [docId], ) if (!res.ok) { throw new Error(await res.text()); @@ -168,17 +176,17 @@ export function RAGPage() { } updateDocuments(); } - const make_remove_function = (docId: string) => { + const make_remove_function = (docId: string, docName: string) => { return () => { - onRemoveDocument(docId); + onRemoveDocument(docId, docName); } } - function DocumentComponent({ doc }: { doc: any }) { + function DocumentComponent({ doc, removable }: { doc: any, removable: boolean }) { return (
{getDocumentName(doc)}
-
+ {removable &&
}
) } @@ -224,13 +232,13 @@ export function RAGPage() {
{Locale.RAG.Documents.DocumentsPrompts}
-
+ {!globalVS &&
-
+
} {(uploading) ? (
@@ -244,7 +252,7 @@ export function RAGPage() {
    {documents.map((doc) => (
  • - +
  • ))}
@@ -327,6 +335,9 @@ export function RAGPage() { if (rag.number_of_results !== undefined) { config.resultNum = rag.number_of_results; } + if (rag.description !== undefined) { + config.description = rag.description; + } } ) break; diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index d6aca00..a262c65 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -143,8 +143,8 @@ export function SideBar(props: { className?: string }) { accessStore.productionInfo === "undefined" ? undefined : (JSON.parse(accessStore.productionInfo) as any) as ProductionInfo; - const kgProdInfo = getKnowledgeGraphInfo(prodInfo); // (prodInfo?.KnowledgeGraph ?? {servers: [], enabled: true}) as DbConfiguration; - const ragProdInfo = getVectorStoreInfo(prodInfo); // (prodInfo?.VectorStore ?? {servers: [], enabled: true}) as DbConfiguration; + const kgProdInfo = getKnowledgeGraphInfo(prodInfo); + const ragProdInfo = getVectorStoreInfo(prodInfo); const mask = getMaskInfo(prodInfo); // drag side bar diff --git a/app/components/welcome.tsx b/app/components/welcome.tsx index 20df381..a2ee31a 100644 --- a/app/components/welcome.tsx +++ b/app/components/welcome.tsx @@ -26,8 +26,8 @@ export function Welcome() { const updateStore = useUpdateStore(); const accessStore = useAccessStore(); - const prodInfo = accessStore.productionInfo === "undefined" ? - undefined : + const prodInfo = accessStore.productionInfo === "undefined" ? + undefined : (JSON.parse(accessStore.productionInfo) as any) as ProductionInfo; const welcome = prodInfo?.Text?.Welcome ?? Locale.Welcome.Page; @@ -98,9 +98,9 @@ export function Welcome() {

About

- {about?.ListTitle} + {about?.Body1}

    - {about?.ListItems.map((listItem: any, index: any) => ( + {about?.ListItems1.map((listItem: any, index: any) => (
  • {listItem}
  • @@ -109,7 +109,16 @@ export function Welcome() {

{about?.Heading2}

- + + {about?.ListItems2 && ( +
    + {about.ListItems2.map((listItem: any, index: any) => ( +
  • + {listItem} +
  • + ))} +
+ )}

@@ -120,9 +129,9 @@ export function Welcome() {

{what}

- {whatMessages && - whatMessages?.length > 0 && - ()} + {whatMessages && + whatMessages?.length > 0 && + ()}
@@ -131,8 +140,8 @@ export function Welcome() {
{howMessages && - howMessages.length > 0 && - ()} + howMessages.length > 0 && + ()}
diff --git a/app/locales/cn.ts b/app/locales/cn.ts index a901c7d..6c89d24 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -114,6 +114,11 @@ const cn = { } } }, + Loading: { + OncoKB: "", + RAG: "", + KG: "", + }, }, Export: { Title: "分享聊天记录", @@ -193,15 +198,15 @@ const cn = { Name: "关于", Title: "关于", Citation: "BioChatter 由 [Sebastian Lobentanzer](https://slobentanzer.github.io/) 开发; 你可以在 [GitHub](https://github.com/biocypher/biochatter-next) 上找到源代码", - ListTitle: "BioChatter 是一种快速将生物医学分析的常见结果置于背景中的工具。 它的工作原理是使用预先训练的语言模型建立主题受限的对话。 这种方法的主要好处是:", - ListItems: [ + Body1: "BioChatter 是一种快速将生物医学分析的常见结果置于背景中的工具。 它的工作原理是使用预先训练的语言模型建立主题受限的对话。 这种方法的主要好处是:", + ListItems1: [ "与流行生物信息学工具(例如 gsea、progeny、de Coupler) 的低维输出集成", "针对生物医学研究和您的具体问题进行提示", "综合保障措施,防止虚假信息并与策划的先验知识进行比较", "共享数据的保密性(与 ChatGPT 界面相反, ChatGPT 界面允许 OpenAI 存储和重用用户的提示)" ], Heading2: "关于型号", - Models: "加载的默认模型是 OpenAIs gpt-3.5-turbo 模型,在标准版本中,每个会话的令牌限制为 4000。 该模型目前有两个版本:0301 和 0613。后者是更新的版本,改进了系统消息的解释和处理函数的功能(以 JSON 形式返回给定函数的属性值)。 此外,OpenAI 还提供了 gpt-3.5-turbo-16k 模型,每个对话的代币限制增加到 16000 个。 该模型稍微昂贵,但对于较长的对话非常有用,特别是在包含文档摘要/提示注入功能时" + Body2: "加载的默认模型是 OpenAIs gpt-3.5-turbo 模型,在标准版本中,每个会话的令牌限制为 4000。 该模型目前有两个版本:0301 和 0613。后者是更新的版本,改进了系统消息的解释和处理函数的功能(以 JSON 形式返回给定函数的属性值)。 此外,OpenAI 还提供了 gpt-3.5-turbo-16k 模型,每个对话的代币限制增加到 16000 个。 该模型稍微昂贵,但对于较长的对话非常有用,特别是在包含文档摘要/提示注入功能时" }, VersionInfo: (x: any) => (`BioChatter Next, 版本: ${x}`), } @@ -490,6 +495,9 @@ const cn = { ResultsNum: { Label: "", subLabel: "", + }, + useReflexion: { + Label: "", } } }, diff --git a/app/locales/en.ts b/app/locales/en.ts index 19ccc67..27eb74f 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -116,6 +116,11 @@ const en: LocaleType = { } } }, + Loading: { + OncoKB: "Calling OncoKB", + RAG: "Calling RAG", + KG: "Calling KG RAG", + }, }, Export: { Title: "Export Messages", @@ -195,9 +200,8 @@ const en: LocaleType = { About: { Name: "About", Title: "About", - Citation: "BioChatter is developed by a multicultural team over on [GitHub](https://github.com/biocypher) ([BioChatter](https://github.com/biocypher/biochatter), [BioChatter Server](https://github.com/biocypher/biochatter-server), [BioChatter Next](https://github.com/biocypher/biochatter-next), [BioChatter Light](https://github.com/biocypher/biochatter-light)), led by [Sebastian Lobentanzer](https://slobentanzer.github.io/). Biochatter Next was developed by Shaohong Feng and Cankun Wang, and is hosted by [BMBL](https://u.osu.edu/bmbl).", - ListTitle: "BioChatter is a tool to integrate biomedical research with current developments in Large Language Models in a user-friendly package. It works by setting up a topic-constrained conversation with a pre-trained language model. Optionally, auxiliary technologies such as knowledge graphs and vector databases can be seamlessly integrated into the conversations. The main benefits of this approach are:", - ListItems: [ + Body1: "BioChatter is a platform to integrate biomedical research with current developments in Large Language Models in a user-friendly package. It works by setting up a topic-constrained conversation with a pre-trained language model. Optionally, auxiliary technologies such as knowledge graphs, vector databases, and external APIs can be seamlessly integrated into the conversations. The main benefits of this approach are:", + ListItems1: [ "Transparency to increase trust in the framework and LLM-driven insights", "Modularity of components: use any model, any prompt, and any database", "Native connectivity to BioCypher knowledge graphs and semantic search via vector database embeddings", @@ -205,7 +209,8 @@ const en: LocaleType = { "Confidentiality of the shared data (as opposed to the ChatGPT interface, which allows storage and reuse of the user's prompts by OpenAI)" ], Heading2: "About the models", - Models: "We offer support of proprietary models via the OpenAI API, as well as open source models via deployment mechanisms such as the Xorbits Inference framework. We also allow running models fully browser based using web assembly integration. You can select models in the settings panel.", + Body2: "We offer support of proprietary models via OpenAI and Anthropic APIs, as well as open source models via deployment mechanisms such as the Xorbits Inference framework and LangChain. We are also working on running models fully browser-based using web assembly integration. You can select available models in the settings panel and context menu. Not all possible models are supported in this preview application.", + Citation: "BioChatter is developed by a multicultural team over on [GitHub](https://github.com/biocypher) ([BioChatter](https://github.com/biocypher/biochatter), [BioChatter Server](https://github.com/biocypher/biochatter-server), [BioChatter Next](https://github.com/biocypher/biochatter-next), [BioChatter Light](https://github.com/biocypher/biochatter-light)), led by [Sebastian Lobentanzer](https://slobentanzer.github.io/). Biochatter Next was developed by Shaohong Feng and Cankun Wang, and is hosted by [BMBL](https://u.osu.edu/bmbl).", }, VersionInfo: (x: any) => (`BioChatter Next, version ${x}`), }, @@ -498,7 +503,10 @@ const en: LocaleType = { ResultsNum: { Label: "Number of results", subLabel: "How many results should be used to supplement the prompts", - } + }, + useReflexion: { + Label: "Use Reflexion", + }, } }, RAG: { diff --git a/app/store/chat.ts b/app/store/chat.ts index b9ce551..e09b9fd 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -20,7 +20,15 @@ import { estimateTokenLength } from "../utils/token"; import { nanoid } from "nanoid"; import { createPersistStore } from "../utils/store"; import { ProductionInfo } from "../utils/datatypes"; -import { getMaskInfo } from "../utils/prodinfo"; +import { + getKnowledgeGraphInfo, + getMaskInfo, + getOncoKBInfo, + getVectorStoreInfo +} from "../utils/prodinfo"; +import { useRAGStore } from "./rag"; +import { useKGStore } from "./kg"; +import { getVectorStoreServerGlobal } from "../utils/rag"; const generateUniqId = () => uuidv4(); @@ -65,6 +73,8 @@ export interface ChatSession { clearContextIndex?: number; mask: Mask; + useAutoAgentSession: boolean; + useOncoKBSession: boolean; useRAGSession: boolean; useKGSession: boolean; contextualPrompts: RagContext[]; @@ -94,6 +104,8 @@ function createEmptySession(mask?: Mask): ChatSession { mask: mask ?? createEmptyMask(), useRAGSession: false, useKGSession: false, + useAutoAgentSession: false, + useOncoKBSession: false, contextualPrompts: [] }; } @@ -322,11 +334,41 @@ export const useChatStore = createPersistStore( botMessage, ]); }); + const strProdInfo = useAccessStore.getState().productionInfo; + const prodInfo = strProdInfo === "undefined" ? undefined : JSON.parse(strProdInfo); + const oncokbInfo = getOncoKBInfo(prodInfo); + const vsInfo = getVectorStoreInfo(prodInfo); + const kgInfo = getKnowledgeGraphInfo(prodInfo); + const ragConfig = vsInfo.enabled ? useRAGStore.getState().currentRAGConfig() : undefined; + const globalVS = ragConfig !== undefined ? + getVectorStoreServerGlobal(ragConfig.connectionArgs, prodInfo?.VectorStore?.servers??[]) : + false; + if (ragConfig && globalVS) { + ragConfig.docIdsWorkspace = undefined; + } + const useRAG = useChatStore.getState().currentSession().useRAGSession; + const useKG = useChatStore.getState().currentSession().useKGSession; + const useOncoKB = useChatStore.getState().currentSession().useOncoKBSession??false; + const useAutoAgent = false; // useChatStore.getState().currentSession().useAutoAgentSession; + const kgConfig = kgInfo.enabled ? useKGStore.getState().config : undefined; + const oncokbConfig = oncokbInfo.enabled ? { + useOncoKB: useOncoKB, + description: oncokbInfo.description, + } : undefined; // make request api.llm.chat({ messages: sendMessages, config: { ...modelConfig, stream: true }, + agentInfo: { + useAutoAgent, + useRAG, + useKG, + useOncoKB, + kgConfig, + ragConfig, + oncokbConfig, + }, onUpdate(message) { botMessage.streaming = true; if (message) { diff --git a/app/store/kg.ts b/app/store/kg.ts index 9475d01..b44abe7 100644 --- a/app/store/kg.ts +++ b/app/store/kg.ts @@ -5,6 +5,8 @@ import { DbConnectionArgs } from "../utils/datatypes"; export interface KGConfig { connectionArgs: DbConnectionArgs; resultNum: number; + description?: string; + useReflexion?: boolean; } export const createEmptyKGConfig = (): KGConfig => ({ diff --git a/app/store/rag.ts b/app/store/rag.ts index b7c5dd6..c56ade6 100644 --- a/app/store/rag.ts +++ b/app/store/rag.ts @@ -9,8 +9,9 @@ export interface RAGConfig { chunkSize: number; overlapSize: number, resultNum: number; - docIdsWorkspace: Array; + docIdsWorkspace?: Array; selectedDocIds?: Array; + description?: string; } export const createEmptyRAGConfig = ( diff --git a/app/utils/datatypes.ts b/app/utils/datatypes.ts index bd9f685..0b21b7a 100644 --- a/app/utils/datatypes.ts +++ b/app/utils/datatypes.ts @@ -4,7 +4,7 @@ export interface DbConnectionArgs { host: string; port: string; user?: string; - password?: string + password?: string; } export interface DbServerSettings { @@ -12,6 +12,8 @@ export interface DbServerSettings { address: string; port?: string; number_of_results?: number; + description: string; + global?: boolean; } export interface DbConfiguration { @@ -34,8 +36,14 @@ export interface TextConfiguration { Masks?: Array; } +export interface APIAgentInfo { + enabled: boolean; + description?: string; +} + export interface ProductionInfo { KnowledgeGraph?: DbConfiguration; VectorStore?: DbConfiguration; + OncoKBAPI?: APIAgentInfo; Text?: TextConfiguration; } diff --git a/app/utils/prodinfo.ts b/app/utils/prodinfo.ts index a5f0468..872c482 100644 --- a/app/utils/prodinfo.ts +++ b/app/utils/prodinfo.ts @@ -1,7 +1,16 @@ import { Mask, createEmptyMask } from "../store/mask"; -import { DbConfiguration, ProductionInfo } from "./datatypes"; +import { + APIAgentInfo, + DbConfiguration, + DbConnectionArgs, + DbServerSettings, + ProductionInfo +} from "./datatypes"; import Locale from "../locales"; +export function getOncoKBInfo(prodInfo?: ProductionInfo): APIAgentInfo { + return (prodInfo?.OncoKBAPI ?? {enabled: true}) +} export function getKnowledgeGraphInfo(prodInfo?: ProductionInfo): DbConfiguration { return (prodInfo?.KnowledgeGraph ?? {servers: [], enabled: true}); @@ -11,6 +20,20 @@ export function getVectorStoreInfo(prodInfo?: ProductionInfo): DbConfiguration { return (prodInfo?.VectorStore ?? {servers: [], enabled: true}); } +export function selectServerInfoFromDbConnectionArgs( + dbConfig: DbConfiguration, + connectionArgs: DbConnectionArgs +): DbServerSettings | undefined { + if (!dbConfig.servers || dbConfig.servers.length === 0) { + return; + } + return dbConfig.servers.find((server) => ( + (server.address === connectionArgs.host || + server.server === connectionArgs.host) && + server.port === connectionArgs.port + )); +} + export function getWelcomeAbout(prodInfo?: ProductionInfo): Record | undefined { if (prodInfo) { return prodInfo?.Text?.Welcome?.About; diff --git a/app/utils/rag.ts b/app/utils/rag.ts index f830e05..d2b50b7 100644 --- a/app/utils/rag.ts +++ b/app/utils/rag.ts @@ -1,21 +1,24 @@ import { DbConnectionArgs, DbServerSettings } from "./datatypes"; -export const getConnectionArgsToDisplay = ( +const getConnectionArgsToDisplay = ( connectionArgs: DbConnectionArgs, - kgServers: Array + kgServers: Array, + defaultPort: string, ): DbConnectionArgs => { for (const server of kgServers) { if (server.server === connectionArgs.host) { return { host: server.server, - port: server.port ?? "7687", + port: `${server.port}` ?? defaultPort, } } + const serverPort = `${server.port??defaultPort}`; + const connectionArgsPort = `${connectionArgs.port??defaultPort}` if (server.address === connectionArgs.host - && (server.port === connectionArgs.port || (server.port === undefined && connectionArgs.port === "7687")) ) { + && (serverPort === connectionArgsPort) ) { return { host: server.server, - port: server.port ?? "7687", + port: serverPort, } } } @@ -23,28 +26,91 @@ export const getConnectionArgsToDisplay = ( return connectionArgs; }; -export const getConnectionArgsToConnect = ( +export const getKGConnectionArgsToDisplay = ( + connectionArgs: DbConnectionArgs, + kgServers: Array, +): DbConnectionArgs => { + return getConnectionArgsToDisplay(connectionArgs, kgServers, "7687"); +} +export const getVectorStoreConnectionArgsToDisplay = ( + connectionArgs: DbConnectionArgs, + vsServers: Array, +): DbConnectionArgs => { + return getConnectionArgsToDisplay(connectionArgs, vsServers, "19530"); +} +export const getVectorStoreServerGlobal = ( + connectionArgs: DbConnectionArgs, + servers: Array, +): boolean => { + const defaultPort = "19530"; + for (const server of servers) { + if (server.server === connectionArgs.host) { + return server.global ?? false; + } + const serverPort = `${server.port??defaultPort}`; + const connectionArgsPort = `${connectionArgs.port??defaultPort}` + if (server.address === connectionArgs.host + && (serverPort === connectionArgsPort) ) { + return server.global ?? false; + } + } + return false; +} + +const getConnectionArgsToConnect = ( connectionArgs: DbConnectionArgs, - kgServers: Array + kgServers: Array, + defaultPort: string, ): DbConnectionArgs => { for (const server of kgServers) { if (server.server === connectionArgs.host) { return { host: server.address, - port: `${server.port ?? 7687}`, + port: `${server.port??defaultPort}`, } } if (server.address === connectionArgs.host - && (server.port === connectionArgs.port || (server.port === undefined && connectionArgs.port === "7687")) ) { + && (server.port === connectionArgs.port + || (server.port === undefined + && connectionArgs.port === defaultPort)) ) { return { host: server.address, - port: `${server.port ?? 7687}`, + port: `${server.port??defaultPort}`, } } } return { ...connectionArgs, - port: `${connectionArgs.port ?? 7687}` } -}; \ No newline at end of file +}; + +export const getKGConnectionArgsToConnect = ( + connectionArgs: DbConnectionArgs, + kgServers: Array +): DbConnectionArgs => { + return getConnectionArgsToConnect(connectionArgs, kgServers, "7687"); +} +export const getVectorStoreConnectionArgsToConnect = ( + connectionArgs: DbConnectionArgs, + vsServers: Array +): DbConnectionArgs => { + return getConnectionArgsToConnect(connectionArgs, vsServers, "19530"); +} + +export const getServerDescription = ( + connectionArgs: DbConnectionArgs, + servers: Array, + defaultPort: string, +): string | undefined => { + for (const server of servers) { + if (server.server === connectionArgs.host) { + return server.description + } + if (server.address === connectionArgs.host + && server.port === connectionArgs.port) { + return server.description + } + } + return undefined; +} \ No newline at end of file diff --git a/config/biochatter-next.yaml b/config/biochatter-next.yaml deleted file mode 100644 index f5c812a..0000000 --- a/config/biochatter-next.yaml +++ /dev/null @@ -1,57 +0,0 @@ -KnowledgeGraph: - enabled: true - servers: - - server: pole-kg - address: biocypher - port: 7687 - -VectorStore: - enabled: true - servers: - - server: local - address: standalone - port: 19530 - -Text: - Welcome: - Title: Cancer Geneticist Assistant - Disclaimer: "This is a use case demonstration, not a final product. The data and information provided here are synthetic (in the case of patient data) or limited by demo access (in the case of the OncoKB API)." - About: - Title: About - Citation: "BioChatter is developed by a multicultural team over on [GitHub](https://github.com/biocypher) ([BioChatter](https://github.com/biocypher/biochatter), [BioChatter Server](https://github.com/biocypher/biochatter-server), [BioChatter Next](https://github.com/biocypher/biochatter-next), [BioChatter Light](https://github.com/biocypher/biochatter-light)), led by [Sebastian Lobentanzer](https://slobentanzer.github.io/). Biochatter Next was developed by Shaohong Feng and Cankun Wang, and is hosted by [BMBL](https://u.osu.edu/bmbl)." - ListTitle: "BioChatter is a tool to integrate biomedical research with current developments in Large Language Models in a user-friendly package. This pre-configured use case is its application to the complex problem of cancer genetics and treatment suggestion. The conversational interface integrates information from various sources and facilitates their interpretation through natural language. The components are:" - ListItems: - - "Results from patient whole genome and RNA sequencing, yielding short variant calls, copy number alterations, gene fusion events, and gene expression levels (synthetic data, available via KG)" - - "External prior knowledge on biological processes, pathways, ... (real public data, available via KG)" - - "External information on the clinical relevance of genetic alterations and approved/pipeline treatments (real public data, demo version limited to few genes, available via API)" - - "Background knownledge of the ovarian cancer studied through semantic search in embeddings of relevant scientific literature (real public data, available via vector database RAG)" - Heading2: How to use - Models: The app is pre-configured with all relevant information. You can ask the chat bot questions about the enriched patient data (KG), the biological background (vector DB), and clinical relevance / treatment suggestions for alterations (API). For connecting to each of the resources, please select the corresponding checkbox in the lower right (KG, RAG, or API). - Masks: - - name: Cancer Genetics Assistant - avatar: "🧑‍🔬" - context: - - id: "cancer-genetics-assistant-1" - role: "system" - content: "You are an assistant to a cancer geneticist." - date: "" - - id: "cancer-genetics-assistant-2" - role: "system" - content: "Your role is to assist the user in the task of analysing patient data and prioritising treatments. You can receive auxiliary information from a knowledge graph containing patient data and molecular knowledge, a vector database with background knowledge, and an API with clinical relevance and treatment suggestions. If you receive input from these sources, include them in your responses, citing the source." - date: "" - - id: "cancer-genetics-assistant-3" - role: "system" - content: "You can ask the user to provide explanations and more background at any time, for instance on the treatment a patient has received, or the experimental background. But for now, wait for the user to ask a question." - date: "" - modelConfig: - model: gpt-3.5-turbo - temperature: 0 - max_tokens: 2000 - presence_penalty: 0 - frequency_penalty: 0 - sendMemory: true - historyMessageCount: 4 - compressMessageLengthThreshold: 2000 - lang: en - builtin: true - createdAt: 1697222692762 diff --git a/docker-compose.yml b/docker-compose.yml index f2d03c7..a77ed0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: flask-app: container_name: biochatter-server - image: biocypher/biochatter-server:0.2.8 + image: biocypher/biochatter-server:0.5.0 env_file: - .bioserver.env ports: