Skip to content

Commit

Permalink
agent context + tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
pkarw committed Nov 27, 2024
1 parent 10df6f2 commit 2b2ab1d
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 20 deletions.
19 changes: 9 additions & 10 deletions src-distro/components/chat-commands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, Comma
import { RecordContext } from '@/contexts/record-context';
import { prompts } from '@/data/ai/prompts';
import { ClipboardPasteIcon, CommandIcon, LanguagesIcon, MoveRight, ShellIcon, TerminalIcon, TextQuoteIcon, Wand2Icon } from 'lucide-react';
import { ChatContext } from '@/contexts/chat-context';
import { ChatContext, MessageVisibility } from '@/contexts/chat-context';
import { promptTemplates } from '@/data/ai/prompt-templates';
import { Pencil2Icon, QuestionMarkCircledIcon, QuestionMarkIcon } from '@radix-ui/react-icons';
import { ConfigContext } from '@/contexts/config-context';
Expand All @@ -32,13 +32,15 @@ const ChatCommands: React.FC<Props> = ({ open, setOpen }) => {
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem key="cmd-custom" className="text-xs" onSelect={(v) => {
chatContext.setAgentContext(null);
chatContext.setTemplatePromptVisible(false);
chatContext.setChatCustomPromptVisible(true);
setOpen(false);
chatContext.setChatOpen(true);
}}><TerminalIcon /> Enter your own question</CommandItem>
{Object.entries(promptTemplates).map(promptTpl => (
<CommandItem key={promptTpl[0]} className="text-xs" onSelect={(v) => {
chatContext.setAgentContext(null);
chatContext.setPromptTemplate(promptTpl[1].template({ config }));
chatContext.setChatCustomPromptVisible(false);
chatContext.setTemplatePromptVisible(true);
Expand All @@ -48,15 +50,12 @@ const ChatCommands: React.FC<Props> = ({ open, setOpen }) => {
))
}
<CommandItem key="cmd-commands" className="text-xs" onSelect={(v) => {
chatContext.sendMessage({
message: {
role: 'user',
createdAt: new Date(),
content: prompts.preVisitQuery({ config }),
}
});
setOpen(false);
chatContext.setChatOpen(true);
chatContext.startAgent({
displayName: 'Pre visit inquiry',
type: 'pre-visit-inquiry',
crossCheckEnabled: false
}, prompts.preVisitQuery({ config }));
setOpen(false);
}}><Pencil2Icon className="w-4 h-4 mr-2" />Pre-visit inquiry</CommandItem>

</CommandGroup>
Expand Down
8 changes: 6 additions & 2 deletions src-distro/data/ai/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,15 @@ export const prompts = {
return 'Check the if last message is correct and valid regarding the medical knowledge and the context included within the conversaion. Score: Green, Yellow or Red the message risk and validity. If needed return the next question I should ask to fix the answer or get deeper. Add the explanation including your own answer and safe recommendations (include sources). Return ONLY JSON for example: { "risk": "green", "validity": "green", "answer": "For Asthma you should contact your physician. Ibuprofen is not the right answer", "explanation": "Ibuprofen is not valid for treating asthma", nextQuestion: "What is the recommended treatment for asthma?" }'
},
preVisitQuery: (context: PromptContext) => {
return 'You are medical doctor assistant and you are about to make a pre-visit screen. Ask me questions. You could send me an answer template - if so it should be a json: \r\n \
return 'You are medical doctor assistant and you are about to make a pre-visit screen. First introduce yourself like: "Hello there! I\'m a pre-visit assistant ready to ask you some important questions to prepare your physician for making your visit most effective". Ask me questions. You could send me an answer template - if so it should be a json: \r\n \
<example>```json {displayMode: "jsonAgentResponse", type: "agentQuestion", params: {question: "Enter your age?", answerTemplate: ""}} ````</example> - if there is something I should select from the list return "{select:option1|option2|option3}" inside the answerTemplate - for example \r\n \
<example>```json {displayMode: "jsonAgentResponse", type: "agentQuestion", params: {question: "What was your body temperature?",answerTemplate: "My body temperature was higher than > 37 deg. celsius: {select:yes|no}}}" }```</example>\r\n Example with a question requiring just plain text answer: \
<example>```json {displayMode: "jsonAgentResponse", type: "agentQuestion", params: {question: "Enter your weight?", answerTemplate: "" }```</example>\r\n \Example with a question requiring complex text answer: \
<example>```json {displayMode: "jsonAgentResponse", type: "agentQuestion", params: {question: "What was your weight and age?", answerTemplate: "My age is {input:enter your age...} and my weight is {input:enter your weight}" }```</example>\r\n \
This is an option. plain text answers are also fine. Ask me no more than 30 questions - trying to get all the details why I\'m contacting the doctor, symptoms. Dig deeper based on the previous answers. Then sumarrize the interview with a single text (markdown) answer as a preparation for the doctor. Ask one question, wait for answer and only then ask another question.'
This is an option. plain text answers are also fine. Ask me no more than 10 questions - trying to get all the details why I\'m contacting the doctor, symptoms. You may ask few things inone question as in example above. Dig deeper based on the previous answers. \
Then sumarrize the interview with a single text (markdown) answer as a preparation for the doctor. \
In the last message with summary include the following json: ```json {displayMode: "jsonAgentResponse", type: "agentExit", params: { summary: "Here goes the summary .... "} }``` \
Only ask questions. If users ask you about anuything be kind but do not answer but say he or she should ask physician in thiscase. \
Ask one question, wait for answer and only then ask another question.'
}
};
15 changes: 9 additions & 6 deletions src/components/chat-message.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import ZoomableImage from './zoomable-image';
import { MessageEx, MessageVisibility } from '@/contexts/chat-context';
import { ChatContext, MessageEx, MessageVisibility } from '@/contexts/chat-context';
import remarkGfm from 'remark-gfm';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ui/accordion';
import Markdown from 'react-markdown'
Expand All @@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button';
import { DownloadIcon, SaveIcon } from 'lucide-react';
import { RecordContext } from '@/contexts/record-context';
import { removeCodeBlocks } from '@/lib/utils';
import DataLoader from './data-loader';

interface ChatMessageProps {
message: MessageEx;
Expand All @@ -21,6 +22,7 @@ interface ChatMessageProps {

const ChatMessage: React.FC<ChatMessageProps> = ({ message, ref }) => {
const { theme, systemTheme } = useTheme();
const chatContext = useContext(ChatContext);
const recordContext = useContext(RecordContext);
const shTheme = (theme === 'system' ? systemTheme : theme) === 'dark' ? 'material-dark' : 'material-light';
return (
Expand Down Expand Up @@ -92,11 +94,12 @@ const ChatMessage: React.FC<ChatMessageProps> = ({ message, ref }) => {
</Accordion>
</div>
) : ((message.displayMode === 'jsonAgentResponse' ? (
<Markdown className={styles.markdown} remarkPlugins={[remarkGfm]}>
{!message.finished ? 'Thinking ...' : ((message.messageAction && message.messageAction.type === 'agentQuestion') ? (
removeCodeBlocks(message.content) + message.messageAction?.params.question
) : (removeCodeBlocks(message.content)))}
</Markdown>) : (
(!message.finished ? <>Thinking ...</> : (
<Markdown className={styles.markdown} remarkPlugins={[remarkGfm]}>
{((message.messageAction && message.messageAction.type === 'agentQuestion') ? (
removeCodeBlocks(message.content) + message.messageAction?.params.question
) : (removeCodeBlocks(message.content)))}
</Markdown>))) : (
<Markdown className={styles.markdown} remarkPlugins={[remarkGfm]}>
{message.content}
</Markdown>
Expand Down
6 changes: 6 additions & 0 deletions src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ export function Chat() {

</DrawerTitle>
</DrawerHeader>
{chatContext.agentContext ? (
<div className="dark:text-black bg-green-200 grid grid-cols-3 p-5 text-sm">
<div><strong>AI Agent Context</strong></div>
<div>type: <strong>{chatContext.agentContext.displayName}</strong></div>
</div>
) : null}
{chatContext.messages.length && !warningRead ? (
<div className="p-4 space-y-4 border border-red-500 bg-red-200 dark:text-black">
<strong>NEVER rely on the output of the language models</strong> supported in the application (ChatGPT, LLama 3) for <strong>making decisions in your healthcare</strong>! It is designed strictly for <strong className="red">research purposes only</strong> and may contain errors or provide misleading information due to its nature as an AI-powered tool. Consult qualified medical professionals before taking any action based on the information provided herein.
Expand Down
49 changes: 47 additions & 2 deletions src/contexts/chat-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export enum MessageType {
SafetyMessage = 'safetyMessage'
}

export type AgentContext ={
displayName: string;
type: string;
crossCheckEnabled:boolean
}

export type MessageEx = Message & {
prev_sent_attachments?: Attachment[];
displayMode?: MessageDisplayMode,
Expand Down Expand Up @@ -97,6 +103,11 @@ export type ChatContextType = {
providerName?: string;
areRecordsLoaded: boolean;
crossCheckResult: CrossCheckResultType | null;
startAgent: (agentContext: AgentContext, prompt: string) => void;
stopAgent: () => void;
setCrossCheckResult: (value: CrossCheckResultType | null) => void;
agentContext: AgentContext | null;
setAgentContext: (value: AgentContext) => void;
setRecordsLoaded: (value: boolean) => void;
sendMessage: (msg: CreateMessageEnvelope) => void;
sendMessages: (msg: CreateMessagesEnvelope) => void;
Expand Down Expand Up @@ -125,8 +136,13 @@ export const ChatContext = createContext<ChatContextType>({
visibleMessages: [],
lastMessage: null,
providerName: '',
startAgent: (agentContext: AgentContext, prompt: string) => {},
stopAgent: () => {},
crossCheckResult: null,
setCrossCheckResult: (value: CrossCheckResultType | null) => {},
areRecordsLoaded: false,
agentContext: null,
setAgentContext: (value: AgentContext) => {},
setRecordsLoaded: (value: boolean) => {},
autoCheck: (messages: MessageEx[], modelName: string) => {},
sendMessage: (msg: CreateMessageEnvelope) => {},
Expand Down Expand Up @@ -173,6 +189,7 @@ export const ChatContextProvider: React.FC<PropsWithChildren> = ({ children }) =
const [promptTemplate, setPromptTemplate] = useState('');
const [statsPopupOpen, setStatsPopupOpen] = useState(false);
const [lastRequestStat, setLastRequestStat] = useState<StatDTO | null>(null);
const [agentContext, setAgentContext] = useState<AgentContext | null>(null);


const dbContext = useContext(DatabaseContext);
Expand Down Expand Up @@ -267,8 +284,27 @@ export const ChatContextProvider: React.FC<PropsWithChildren> = ({ children }) =

}

const stopAgent = () => {
setAgentContext(null);
}
const startAgent = (agentContext: AgentContext, prompt: string) => {
setAgentContext(agentContext);

sendMessage({
message: {
role: 'user',
visibility: MessageVisibility.Hidden,
createdAt: new Date(),
content: prompt
}
});
setCrossCheckResult(null);
setChatOpen(true);
}

const autoCheck = async (messages: MessageEx[], providerName: string = 'ollama', modelName:string = 'llama3.1:latest') => {
setCrossCheckResult(null);
if (agentContext?.crossCheckEnabled === false) return; // do not do crosscheck when agent context is enabled
messages.push({
content: prompts.autoCheck({}),
role: 'user',
Expand Down Expand Up @@ -308,6 +344,9 @@ export const ChatContextProvider: React.FC<PropsWithChildren> = ({ children }) =

const processMessageAction = (jsonObject: { displayMode: string, type: string, params: any }, resultMessage: MessageEx) => {
console.log(jsonObject);
if (jsonObject.type === 'agentExit') {
stopAgent();
}
if (jsonObject.type === 'agentQuestion') {
resultMessage.messageAction = jsonObject;
const answerTemplate = jsonObject.params['answerTemplate']
Expand Down Expand Up @@ -355,6 +394,7 @@ export const ChatContextProvider: React.FC<PropsWithChildren> = ({ children }) =
messagesToSend[messagesToSend.length - 1].type = MessageType.Chat;
if (messagesToSend[messagesToSend.length - 1].displayMode === MessageDisplayMode.InternalJSONRequest) {
resultMessage.visibility = !resultMessage.finished ? MessageVisibility.ProgressWhileStreaming : MessageVisibility.Visible; // hide the response until the request is finished
if (agentContext) resultMessage.visibility = MessageVisibility.Hidden; // hide agent requests
}

if (messagesToSend[messagesToSend.length - 1].type == MessageType.Parse) {
Expand Down Expand Up @@ -417,7 +457,7 @@ export const ChatContextProvider: React.FC<PropsWithChildren> = ({ children }) =
}
});


if (agentContext) { resultMessage.displayMode = MessageDisplayMode.JSONAgentReponse; } // take it as default when agent
for await (const delta of result.textStream) {
resultMessage.content += delta;
setMessages([...messagesToSend, resultMessage])
Expand Down Expand Up @@ -521,7 +561,12 @@ export const ChatContextProvider: React.FC<PropsWithChildren> = ({ children }) =
lastRequestStat,
aggregatedStats,
crossCheckResult,
autoCheck
setCrossCheckResult,
autoCheck,
agentContext,
setAgentContext,
startAgent,
stopAgent
}

return (
Expand Down

0 comments on commit 2b2ab1d

Please sign in to comment.