Plumbed cache through agents
Removed some dead frontend code
This commit is contained in:
parent
2fe1f5b181
commit
80b63fe0e1
@ -1,41 +1,8 @@
|
||||
import React, {
|
||||
useState,
|
||||
useImperativeHandle,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Button from '@mui/material/Button';
|
||||
import Box from '@mui/material/Box';
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import React from 'react';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
|
||||
import { Message } from './Message';
|
||||
import { DeleteConfirmation } from 'components/DeleteConfirmation';
|
||||
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { StreamingResponse } from 'services/api-client';
|
||||
import {
|
||||
ChatMessage,
|
||||
ChatContext,
|
||||
ChatSession,
|
||||
ChatQuery,
|
||||
ChatMessageUser,
|
||||
ChatMessageError,
|
||||
ChatMessageStreaming,
|
||||
ChatMessageStatus,
|
||||
ChatMessageMetaData,
|
||||
} from 'types/types';
|
||||
import { PaginatedResponse } from 'types/conversion';
|
||||
|
||||
import './Conversation.css';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import { ChatMessage, ChatQuery, ChatMessageMetaData } from 'types/types';
|
||||
|
||||
const defaultMessage: ChatMessage = {
|
||||
status: 'done',
|
||||
@ -47,11 +14,6 @@ const defaultMessage: ChatMessage = {
|
||||
metadata: null as unknown as ChatMessageMetaData,
|
||||
};
|
||||
|
||||
const loadingMessage: ChatMessage = {
|
||||
...defaultMessage,
|
||||
content: 'Establishing connection with server...',
|
||||
};
|
||||
|
||||
type ConversationMode = 'chat' | 'job_description' | 'resume' | 'fact_check' | 'persona';
|
||||
|
||||
interface ConversationHandle {
|
||||
@ -77,417 +39,4 @@ interface ConversationProps extends BackstoryElementProps {
|
||||
onResponse?: ((message: ChatMessage) => void) | undefined; // Event called when a query completes (provides messages)
|
||||
}
|
||||
|
||||
const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
(props: ConversationProps, ref) => {
|
||||
const {
|
||||
actionLabel,
|
||||
defaultPrompts,
|
||||
hideDefaultPrompts,
|
||||
hidePreamble,
|
||||
messageFilter,
|
||||
messages,
|
||||
onResponse,
|
||||
placeholder,
|
||||
preamble,
|
||||
resetLabel,
|
||||
sx,
|
||||
} = props;
|
||||
const { apiClient } = useAuth();
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [conversation, setConversation] = useState<ChatMessage[]>([]);
|
||||
const conversationRef = useRef<ChatMessage[]>([]);
|
||||
const [filteredConversation, setFilteredConversation] = useState<ChatMessage[]>([]);
|
||||
const [processingMessage, setProcessingMessage] = useState<ChatMessage | undefined>(undefined);
|
||||
const [streamingMessage, setStreamingMessage] = useState<ChatMessage | undefined>(undefined);
|
||||
const [noInteractions, setNoInteractions] = useState<boolean>(true);
|
||||
const viewableElementRef = useRef<HTMLDivElement>(null);
|
||||
const backstoryTextRef = useRef<BackstoryTextFieldRef>(null);
|
||||
const stopRef = useRef(false);
|
||||
const controllerRef = useRef<StreamingResponse>(null);
|
||||
const [chatSession, setChatSession] = useState<ChatSession | null>(null);
|
||||
const { setSnack } = useAppState();
|
||||
|
||||
// Keep the ref updated whenever items changes
|
||||
useEffect(() => {
|
||||
conversationRef.current = conversation;
|
||||
}, [conversation]);
|
||||
|
||||
// Update the context status
|
||||
/* Transform the 'Conversation' by filtering via callback, then adding
|
||||
* preamble and messages based on whether the conversation
|
||||
* has any elements yet */
|
||||
useEffect(() => {
|
||||
let filtered = [];
|
||||
if (messageFilter === undefined) {
|
||||
filtered = conversation;
|
||||
// console.log('No message filter provided. Using all messages.', filtered);
|
||||
} else {
|
||||
//console.log('Filtering conversation...')
|
||||
filtered =
|
||||
messageFilter(conversation); /* Do not copy conversation or useEffect will loop forever */
|
||||
//console.log(`${conversation.length - filtered.length} messages filtered out.`);
|
||||
}
|
||||
if (filtered.length === 0) {
|
||||
setFilteredConversation([...(preamble || []), ...(messages || [])]);
|
||||
} else {
|
||||
setFilteredConversation([
|
||||
...(hidePreamble ? [] : preamble || []),
|
||||
...(messages || []),
|
||||
...filtered,
|
||||
]);
|
||||
}
|
||||
}, [conversation, setFilteredConversation, messageFilter, preamble, messages, hidePreamble]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatSession) {
|
||||
return;
|
||||
}
|
||||
const createChatSession = async (): Promise<void> => {
|
||||
try {
|
||||
const chatContext: ChatContext = { type: 'general' };
|
||||
const response: ChatSession = await apiClient.createChatSession(chatContext);
|
||||
setChatSession(response);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setSnack('Unable to create chat session.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
createChatSession();
|
||||
}, [chatSession, setChatSession, apiClient, setSnack]);
|
||||
|
||||
const getChatMessages = useCallback(async () => {
|
||||
if (!chatSession || !chatSession.id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response: PaginatedResponse<ChatMessage> = await apiClient.getChatMessages(
|
||||
chatSession.id
|
||||
);
|
||||
const messages: ChatMessage[] = response.data;
|
||||
|
||||
setProcessingMessage(undefined);
|
||||
setStreamingMessage(undefined);
|
||||
|
||||
if (messages.length === 0) {
|
||||
console.log(`History returned with 0 entries`);
|
||||
setConversation([]);
|
||||
setNoInteractions(true);
|
||||
} else {
|
||||
console.log(`History returned with ${messages.length} entries:`, messages);
|
||||
setConversation(messages);
|
||||
setNoInteractions(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Unable to obtain chat history', error);
|
||||
setProcessingMessage({
|
||||
...defaultMessage,
|
||||
status: 'error',
|
||||
content: `Unable to obtain history from server.`,
|
||||
});
|
||||
setTimeout(() => {
|
||||
setProcessingMessage(undefined);
|
||||
setNoInteractions(true);
|
||||
}, 3000);
|
||||
setSnack('Unable to obtain chat history.', 'error');
|
||||
}
|
||||
}, [chatSession, apiClient, setSnack]);
|
||||
|
||||
// Set the initial chat history to "loading" or the welcome message if loaded.
|
||||
useEffect(() => {
|
||||
if (!chatSession) {
|
||||
setProcessingMessage(loadingMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessingMessage(undefined);
|
||||
setStreamingMessage(undefined);
|
||||
setConversation([]);
|
||||
setNoInteractions(true);
|
||||
|
||||
getChatMessages();
|
||||
}, [chatSession, getChatMessages]);
|
||||
|
||||
const handleEnter = (value: string): void => {
|
||||
const query: ChatQuery = {
|
||||
prompt: value,
|
||||
};
|
||||
processQuery(query);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submitQuery: (query: ChatQuery): void => {
|
||||
processQuery(query);
|
||||
},
|
||||
fetchHistory: (): void => {
|
||||
getChatMessages();
|
||||
},
|
||||
}));
|
||||
|
||||
// const reset = async () => {
|
||||
// try {
|
||||
// const response = await fetch(connectionBase + `/api/reset/${sessionId}/${type}`, {
|
||||
// method: 'PUT',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'Accept': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify({ reset: ['history'] })
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
|
||||
// }
|
||||
|
||||
// if (!response.body) {
|
||||
// throw new Error('Response body is null');
|
||||
// }
|
||||
|
||||
// setProcessingMessage(undefined);
|
||||
// setStreamingMessage(undefined);
|
||||
// setConversation([]);
|
||||
// setNoInteractions(true);
|
||||
|
||||
// } catch (e) {
|
||||
// setSnack("Error resetting history", "error")
|
||||
// console.error('Error resetting history:', e);
|
||||
// }
|
||||
// };
|
||||
|
||||
const cancelQuery = (): void => {
|
||||
console.log('Stop query');
|
||||
if (controllerRef.current) {
|
||||
controllerRef.current.cancel();
|
||||
}
|
||||
controllerRef.current = null;
|
||||
};
|
||||
|
||||
const processQuery = (query: ChatQuery): void => {
|
||||
if (controllerRef.current || !chatSession || !chatSession.id) {
|
||||
return;
|
||||
}
|
||||
setNoInteractions(false);
|
||||
setConversation([
|
||||
...conversationRef.current,
|
||||
{
|
||||
...defaultMessage,
|
||||
type: 'text',
|
||||
content: query.prompt,
|
||||
},
|
||||
]);
|
||||
setProcessing(true);
|
||||
setProcessingMessage({
|
||||
...defaultMessage,
|
||||
content: 'Submitting request...',
|
||||
});
|
||||
|
||||
const chatMessage: ChatMessageUser = {
|
||||
role: 'user',
|
||||
sessionId: chatSession.id,
|
||||
content: query.prompt,
|
||||
tunables: query.tunables,
|
||||
status: 'done',
|
||||
type: 'text',
|
||||
timestamp: new Date(),
|
||||
};
|
||||
|
||||
controllerRef.current = apiClient.sendMessageStream(chatMessage, {
|
||||
onMessage: (msg: ChatMessage) => {
|
||||
console.log('onMessage:', msg);
|
||||
setConversation([...conversationRef.current, msg]);
|
||||
setStreamingMessage(undefined);
|
||||
setProcessingMessage(undefined);
|
||||
setProcessing(false);
|
||||
if (onResponse) {
|
||||
onResponse(msg);
|
||||
}
|
||||
},
|
||||
onError: (error: string | ChatMessageError) => {
|
||||
console.log('onError:', error);
|
||||
// Type-guard to determine if this is a ChatMessageBase or a string
|
||||
if (typeof error === 'object' && error !== null && 'content' in error) {
|
||||
setProcessingMessage(error as ChatMessage);
|
||||
setProcessing(false);
|
||||
controllerRef.current = null;
|
||||
} else {
|
||||
setProcessingMessage({
|
||||
...defaultMessage,
|
||||
content: error as string,
|
||||
});
|
||||
}
|
||||
},
|
||||
onStreaming: (chunk: ChatMessageStreaming) => {
|
||||
console.log('onStreaming:', chunk);
|
||||
setStreamingMessage({ ...defaultMessage, ...chunk });
|
||||
},
|
||||
onStatus: (status: ChatMessageStatus) => {
|
||||
console.log('onStatus:', status);
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log('onComplete');
|
||||
controllerRef.current = null;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!chatSession) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
// <Scrollable
|
||||
// className={`${className || ""} Conversation`}
|
||||
// autoscroll
|
||||
// textFieldRef={viewableElementRef}
|
||||
// fallbackThreshold={0.5}
|
||||
// sx={{
|
||||
// p: 1,
|
||||
// mt: 0,
|
||||
// ...sx
|
||||
// }}
|
||||
// >
|
||||
<Box
|
||||
className="Conversation"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
minHeight: 'max-content',
|
||||
height: 'max-content',
|
||||
maxHeight: 'max-content',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 1, mt: 0, ...sx }}>
|
||||
{filteredConversation.map((message, index) => (
|
||||
<Message key={index} {...{ chatSession, sendQuery: processQuery, message }} />
|
||||
))}
|
||||
{processingMessage !== undefined && (
|
||||
<Message
|
||||
{...{
|
||||
chatSession,
|
||||
sendQuery: processQuery,
|
||||
message: processingMessage,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{streamingMessage !== undefined && (
|
||||
<Message
|
||||
{...{
|
||||
chatSession,
|
||||
sendQuery: processQuery,
|
||||
message: streamingMessage,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
m: 1,
|
||||
}}
|
||||
>
|
||||
<PropagateLoader
|
||||
size="10px"
|
||||
loading={processing}
|
||||
aria-label="Loading Spinner"
|
||||
data-testid="loader"
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
className="Query"
|
||||
sx={{ display: 'flex', flexDirection: 'column', p: 1, flexGrow: 1 }}
|
||||
>
|
||||
{placeholder && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
p: 0,
|
||||
m: 0,
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
ref={viewableElementRef}
|
||||
>
|
||||
<BackstoryTextField
|
||||
ref={backstoryTextRef}
|
||||
disabled={processing}
|
||||
onEnter={handleEnter}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
key="jobActions"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<DeleteConfirmation
|
||||
label={resetLabel || 'all data'}
|
||||
disabled={!chatSession || processingMessage !== undefined || noInteractions}
|
||||
onDelete={(): void => {
|
||||
/*reset(); resetAction && resetAction(); */
|
||||
}}
|
||||
/>
|
||||
<Tooltip title={actionLabel || 'Send'}>
|
||||
<span style={{ display: 'flex', flexGrow: 1 }}>
|
||||
<Button
|
||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||
variant="contained"
|
||||
disabled={!chatSession || processingMessage !== undefined}
|
||||
onClick={(): void => {
|
||||
processQuery({
|
||||
prompt:
|
||||
(backstoryTextRef.current &&
|
||||
backstoryTextRef.current.getAndResetValue()) ||
|
||||
'',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{actionLabel}
|
||||
<SendIcon />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title="Cancel">
|
||||
<span style={{ display: 'flex' }}>
|
||||
{' '}
|
||||
{/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
||||
<IconButton
|
||||
aria-label="cancel"
|
||||
onClick={(): void => {
|
||||
cancelQuery();
|
||||
}}
|
||||
sx={{ display: 'flex', margin: 'auto 0px' }}
|
||||
size="large"
|
||||
edge="start"
|
||||
disabled={stopRef.current || !chatSession || processing === false}
|
||||
>
|
||||
<CancelIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
{(noInteractions || !hideDefaultPrompts) &&
|
||||
defaultPrompts !== undefined &&
|
||||
defaultPrompts.length !== 0 && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{defaultPrompts.map((element, index) => {
|
||||
return <Box key={index}>{element}</Box>;
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', flexGrow: 1 }}></Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
Conversation.displayName = 'Conversation';
|
||||
export type { ConversationProps, ConversationHandle };
|
||||
|
||||
export { Conversation };
|
||||
|
@ -1,67 +0,0 @@
|
||||
import React, { forwardRef, useEffect, useState } from 'react';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MuiMarkdown from 'mui-markdown';
|
||||
|
||||
import { BackstoryPageProps } from '../components/BackstoryTab';
|
||||
import { Conversation, ConversationHandle } from '../components/Conversation';
|
||||
import { BackstoryQuery } from '../components/BackstoryQuery';
|
||||
import { CandidateInfo } from 'components/ui/CandidateInfo';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { Candidate } from 'types/types';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import * as Types from 'types/types';
|
||||
|
||||
const ChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
|
||||
(props: BackstoryPageProps, ref) => {
|
||||
const { setSnack } = useAppState();
|
||||
const { user } = useAuth();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [questions, setQuestions] = useState<React.ReactElement[]>([]);
|
||||
const candidate: Candidate | null =
|
||||
user?.userType === 'candidate' ? (user as Types.Candidate) : null;
|
||||
|
||||
// console.log("ChatPage candidate =>", candidate);
|
||||
useEffect(() => {
|
||||
if (!candidate) {
|
||||
return;
|
||||
}
|
||||
|
||||
setQuestions([
|
||||
<Box sx={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row' }}>
|
||||
{candidate.questions?.map((q, i: number) => (
|
||||
<BackstoryQuery key={i} question={q} />
|
||||
))}
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
<MuiMarkdown>
|
||||
{`As with all LLM interactions, the results may not be 100% accurate. Please contact **${candidate.fullName}** if you have any questions.`}
|
||||
</MuiMarkdown>
|
||||
</Box>,
|
||||
]);
|
||||
}, [candidate, isMobile]);
|
||||
|
||||
if (!candidate) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<CandidateInfo candidate={candidate} action="Chat with Backstory AI about " />
|
||||
<Conversation
|
||||
ref={ref}
|
||||
{...{
|
||||
multiline: true,
|
||||
type: 'chat',
|
||||
placeholder: `What would you like to know about ${candidate?.firstName}?`,
|
||||
resetLabel: 'chat',
|
||||
defaultPrompts: questions,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { ChatPage };
|
@ -1,6 +1,6 @@
|
||||
// Generated TypeScript types from Pydantic models
|
||||
// Source: src/backend/models.py
|
||||
// Generated on: 2025-07-01T22:28:07.615325
|
||||
// Generated on: 2025-07-09T20:57:47.233946
|
||||
// DO NOT EDIT MANUALLY - This file is auto-generated
|
||||
|
||||
// ============================
|
||||
|
@ -236,11 +236,6 @@ class Agent(BaseModel, ABC):
|
||||
# context_size is shared across all subclasses
|
||||
_context_size: ClassVar[int] = int(defines.max_context * 0.5)
|
||||
|
||||
conversation: List[ChatMessageUser] = Field(
|
||||
default_factory=list,
|
||||
description="Conversation history for this agent, used to maintain context across messages.",
|
||||
)
|
||||
|
||||
@property
|
||||
def context_size(self) -> int:
|
||||
return Agent._context_size
|
||||
@ -519,22 +514,30 @@ Content: {content}
|
||||
chroma_results = await user.file_watcher.find_similar(query=prompt, top_k=top_k, threshold=threshold)
|
||||
if not chroma_results:
|
||||
continue
|
||||
query_embedding = np.array(chroma_results["query_embedding"]).flatten() # type: ignore
|
||||
|
||||
umap_2d = user.file_watcher.umap_model_2d.transform([query_embedding])[0] # type: ignore
|
||||
umap_3d = user.file_watcher.umap_model_3d.transform([query_embedding])[0] # type: ignore
|
||||
query_embedding = np.array(chroma_results["query_embedding"]).flatten()
|
||||
umap_2d = []
|
||||
umap_3d = []
|
||||
if user.file_watcher.umap_model_2d and user.file_watcher.umap_model_3d:
|
||||
umap_2d = user.file_watcher.umap_model_2d.transform([query_embedding])[0].tolist() # type: ignore
|
||||
umap_3d = user.file_watcher.umap_model_3d.transform([query_embedding])[0].tolist() # type: ignore
|
||||
|
||||
rag_metadata = ChromaDBGetResponse(
|
||||
name=rag.name,
|
||||
query=prompt,
|
||||
query_embedding=query_embedding.tolist(),
|
||||
size=user.file_watcher.collection.count(),
|
||||
ids=chroma_results.get("ids", []),
|
||||
distances=chroma_results.get("distances", []),
|
||||
embeddings=chroma_results.get("embeddings", []),
|
||||
documents=chroma_results.get("documents", []),
|
||||
metadatas=chroma_results.get("metadatas", []),
|
||||
umap_embedding_2d=umap_2d.tolist(),
|
||||
umap_embedding_3d=umap_3d.tolist(),
|
||||
umap_embedding_2d=umap_2d,
|
||||
umap_embedding_3d=umap_3d,
|
||||
)
|
||||
if rag_metadata.query_embedding:
|
||||
logger.info(f"Len of embedding: {len(rag_metadata.query_embedding)}")
|
||||
else:
|
||||
logger.warning(f"No query embedding found in RAG results; {query_embedding}")
|
||||
results.append(rag_metadata)
|
||||
except Exception as e:
|
||||
continue_message = ChatMessageStatus(
|
||||
@ -552,6 +555,8 @@ Content: {content}
|
||||
yield final_message
|
||||
return
|
||||
|
||||
# Send a single message to the LLM and return the response
|
||||
# This is a one-shot generation method that does not maintain conversation history.
|
||||
async def llm_one_shot(
|
||||
self,
|
||||
llm: Any,
|
||||
@ -636,7 +641,14 @@ Content: {content}
|
||||
return
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ApiMessage, None]:
|
||||
if not self.user:
|
||||
error_message = ChatMessageError(session_id=session_id, content="No user set for chat generation.")
|
||||
@ -648,11 +660,26 @@ Content: {content}
|
||||
content=prompt,
|
||||
)
|
||||
|
||||
await database.add_chat_message(session_id, user_message.model_dump())
|
||||
logger.info(f"💬 User message saved to database for session {session_id}")
|
||||
|
||||
# Create a pruned down message list based purely on the prompt and responses,
|
||||
# discarding the full preamble generated by prepare_message
|
||||
messages: List[LLMMessage] = [LLMMessage(role="system", content=self.system_prompt)]
|
||||
# Add the conversation history to the messages
|
||||
messages.extend(
|
||||
[
|
||||
LLMMessage(role=m["role"], content=m["content"])
|
||||
for m in await database.get_recent_chat_messages(session_id=session_id)
|
||||
]
|
||||
)
|
||||
|
||||
self.user.metrics.generate_count.labels(agent=self.agent_type).inc()
|
||||
with self.user.metrics.generate_duration.labels(agent=self.agent_type).time():
|
||||
context = None
|
||||
rag_message: ChatMessageRagSearch | None = None
|
||||
if self.user:
|
||||
logger.info("Generating RAG results")
|
||||
message = None
|
||||
async for message in self.generate_rag_results(session_id=session_id, prompt=prompt):
|
||||
if message.status == ApiStatusType.ERROR:
|
||||
@ -668,16 +695,6 @@ Content: {content}
|
||||
rag_message = message
|
||||
context = self.get_rag_context(rag_message)
|
||||
|
||||
# Create a pruned down message list based purely on the prompt and responses,
|
||||
# discarding the full preamble generated by prepare_message
|
||||
messages: List[LLMMessage] = [LLMMessage(role="system", content=self.system_prompt)]
|
||||
# Add the conversation history to the messages
|
||||
messages.extend(
|
||||
[
|
||||
LLMMessage(role="user" if isinstance(m, ChatMessageUser) else "assistant", content=m.content)
|
||||
for m in self.conversation
|
||||
]
|
||||
)
|
||||
# Add the RAG context to the messages if available
|
||||
if context:
|
||||
messages.append(
|
||||
@ -804,7 +821,7 @@ Content: {content}
|
||||
num_ctx=self.context_size,
|
||||
temperature=temperature,
|
||||
)
|
||||
logger.info(f"Message options: {options.model_dump(exclude_unset=True)}")
|
||||
logger.info(f"Message options: {options.model_dump(exclude_unset=True)} with {len(messages)} messages")
|
||||
content = ""
|
||||
start_time = time.perf_counter()
|
||||
response = None
|
||||
@ -851,6 +868,7 @@ Content: {content}
|
||||
prompt_eval_count=response.usage.prompt_eval_count,
|
||||
prompt_eval_duration=response.usage.prompt_eval_duration,
|
||||
rag_results=rag_message.content if rag_message else [],
|
||||
llm_history=messages,
|
||||
timers={
|
||||
"llm_streamed": end_time - start_time,
|
||||
"llm_with_tools": 0, # Placeholder for tool processing time
|
||||
@ -858,9 +876,10 @@ Content: {content}
|
||||
),
|
||||
)
|
||||
|
||||
await database.add_chat_message(session_id, chat_message.model_dump())
|
||||
logger.info(f"🤖 Assistent response saved to database for session {session_id}")
|
||||
|
||||
# Add the user and chat messages to the conversation
|
||||
self.conversation.append(user_message)
|
||||
self.conversation.append(chat_message)
|
||||
yield chat_message
|
||||
return
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
from __future__ import annotations
|
||||
from typing import Literal, AsyncGenerator, ClassVar, Optional, Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from database.core import RedisDatabase
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
from logger import logger
|
||||
|
||||
from .registry import agent_registry
|
||||
from models import ApiMessage, Tunables, ApiStatusType
|
||||
from models import ApiMessage, Tunables, ApiStatusType, LLMMessage
|
||||
|
||||
|
||||
system_message = """
|
||||
@ -16,7 +19,7 @@ When answering queries, follow these steps:
|
||||
- If there is information in the <|context|> section to enhance the answer, incorporate it seamlessly and refer to it as 'the latest information' or 'recent data' instead of mentioning '<|context|>' (etc.) or quoting it directly.
|
||||
- Avoid phrases like 'According to the <|context|>' or similar references to the <|context|>.
|
||||
|
||||
Always <|context|> when possible. Be concise, and never make up information. If you do not know the answer, say so.
|
||||
Always use <|context|> when possible. Be concise, and never make up information. If you do not know the answer, say so.
|
||||
|
||||
Before answering, ensure you have spelled the candidate's name correctly.
|
||||
"""
|
||||
@ -31,9 +34,17 @@ class CandidateChat(Agent):
|
||||
_agent_type: ClassVar[str] = agent_type # Add this for registration
|
||||
|
||||
system_prompt: str = system_message
|
||||
sessions: dict[str, list[LLMMessage]] = Field(default_factory=dict)
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ApiMessage, None]:
|
||||
user = self.user
|
||||
if not user:
|
||||
@ -51,9 +62,17 @@ Use that spelling instead of any spelling you may find in the <|context|>.
|
||||
|
||||
{system_message}
|
||||
"""
|
||||
if session_id not in self.sessions:
|
||||
self.sessions[session_id] = [LLMMessage(role="user", content=prompt)]
|
||||
|
||||
async for message in super().generate(
|
||||
llm=llm, model=model, session_id=session_id, prompt=prompt, temperature=temperature, tunables=tunables
|
||||
llm=llm,
|
||||
model=model,
|
||||
session_id=session_id,
|
||||
prompt=prompt,
|
||||
database=database,
|
||||
temperature=temperature,
|
||||
tunables=tunables,
|
||||
):
|
||||
if message.status == ApiStatusType.ERROR:
|
||||
yield message
|
||||
|
@ -1,86 +0,0 @@
|
||||
from __future__ import annotations
|
||||
from typing import Literal, ClassVar
|
||||
from datetime import datetime
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
|
||||
from .registry import agent_registry
|
||||
|
||||
system_message = f"""
|
||||
Launched on {datetime.now().isoformat()}.
|
||||
|
||||
When answering queries, follow these steps:
|
||||
|
||||
- First analyze the query to determine if real-time information from the tools might be helpful
|
||||
- Even when <|context|> or <|resume|> is provided, consider whether the tools would provide more current or comprehensive information
|
||||
- Use the provided tools whenever they would enhance your response, regardless of whether context is also available
|
||||
- When presenting weather forecasts, include relevant emojis immediately before the corresponding text. For example, for a sunny day, say \"☀️ Sunny\" or if the forecast says there will be \"rain showers, say \"🌧️ Rain showers\". Use this mapping for weather emojis: Sunny: ☀️, Cloudy: ☁️, Rainy: 🌧️, Snowy: ❄️
|
||||
- When any combination of <|context|>, <|resume|> and tool outputs are relevant, synthesize information from all sources to provide the most complete answer
|
||||
- Always prioritize the most up-to-date and relevant information, whether it comes from <|context|>, <|resume|> or tools
|
||||
- If <|context|> and tool outputs contain conflicting information, prefer the tool outputs as they likely represent more current data
|
||||
- If there is information in the <|context|> or <|resume|> sections to enhance the answer, incorporate it seamlessly and refer to it as 'the latest information' or 'recent data' instead of mentioning '<|context|>' (etc.) or quoting it directly.
|
||||
- Avoid phrases like 'According to the <|context|>' or similar references to the <|context|> or <|resume|>.
|
||||
|
||||
CRITICAL INSTRUCTIONS FOR IMAGE GENERATION:
|
||||
|
||||
1. When the user requests to generate an image, inject the following into the response: <GenerateImage prompt="USER-PROMPT"/>. Do this when users request images, drawings, or visual content.
|
||||
3. MANDATORY: You must respond with EXACTLY this format: <GenerateImage prompt="{{USER-PROMPT}}"/>
|
||||
4. FORBIDDEN: DO NOT use markdown image syntax 
|
||||
5. FORBIDDEN: DO NOT create fake URLs or file paths
|
||||
6. FORBIDDEN: DO NOT use any other image embedding format
|
||||
|
||||
CORRECT EXAMPLE:
|
||||
User: "Draw a cat"
|
||||
Your response: "<GenerateImage prompt='Draw a cat'/>"
|
||||
|
||||
WRONG EXAMPLES (DO NOT DO THIS):
|
||||
- 
|
||||
- 
|
||||
- <img src="...">
|
||||
|
||||
The <GenerateImage prompt="{{USER-PROMPT}}"/> format is the ONLY way to display images in this system.
|
||||
DO NOT make up a URL for an image or provide markdown syntax for embedding an image. Only use <GenerateImage prompt="{{USER-PROMPT}}".
|
||||
|
||||
Always use tools, <|resume|>, and <|context|> when possible. Be concise, and never make up information. If you do not know the answer, say so.
|
||||
"""
|
||||
|
||||
|
||||
class Chat(Agent):
|
||||
"""
|
||||
Chat Agent
|
||||
"""
|
||||
|
||||
agent_type: Literal["general"] = "general" # type: ignore
|
||||
_agent_type: ClassVar[str] = agent_type # Add this for registration
|
||||
|
||||
system_prompt: str = system_message
|
||||
|
||||
|
||||
# async def prepare_message(self, message: Message) -> AsyncGenerator[Message, None]:
|
||||
# logger.info(f"{self.agent_type} - {inspect.stack()[0].function}")
|
||||
# if not self.context:
|
||||
# raise ValueError("Context is not set for this agent.")
|
||||
|
||||
# async for message in super().prepare_message(message):
|
||||
# if message.status != "done":
|
||||
# yield message
|
||||
|
||||
# if message.preamble:
|
||||
# excluded = {}
|
||||
# preamble_types = [
|
||||
# f"<|{p}|>" for p in message.preamble.keys() if p not in excluded
|
||||
# ]
|
||||
# preamble_types_AND = " and ".join(preamble_types)
|
||||
# preamble_types_OR = " or ".join(preamble_types)
|
||||
# message.preamble[
|
||||
# "rules"
|
||||
# ] = f"""\
|
||||
# - Answer the question based on the information provided in the {preamble_types_AND} sections by incorporate it seamlessly and refer to it using natural language instead of mentioning {preamble_types_OR} or quoting it directly.
|
||||
# - If there is no information in these sections, answer based on your knowledge, or use any available tools.
|
||||
# - Avoid phrases like 'According to the {preamble_types[0]}' or similar references to the {preamble_types_OR}.
|
||||
# """
|
||||
# message.preamble["question"] = "Respond to:"
|
||||
|
||||
|
||||
# Register the base agent
|
||||
agent_registry.register(Chat._agent_type, Chat)
|
@ -9,9 +9,10 @@ from typing import (
|
||||
) # NOTE: You must import Optional for late binding to work
|
||||
import random
|
||||
import time
|
||||
import time
|
||||
import os
|
||||
|
||||
from database.core import RedisDatabase
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
from models import (
|
||||
ApiActivityType,
|
||||
@ -40,7 +41,14 @@ class ImageGenerator(Agent):
|
||||
system_prompt: str = "" # No system prompt is used
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ChatMessage | ChatMessageStatus | ChatMessageError | ChatMessageStreaming, None]:
|
||||
if not self.user:
|
||||
logger.error("User is not set for ImageGenerator agent.")
|
||||
|
@ -17,6 +17,8 @@ import json
|
||||
import time
|
||||
import os
|
||||
|
||||
from database.core import RedisDatabase
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
from models import (
|
||||
ApiActivityType,
|
||||
@ -303,7 +305,14 @@ class GeneratePersona(Agent):
|
||||
self.full_name = f"{self.first_name} {self.last_name}"
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ChatMessage | ChatMessageStatus | ChatMessageError | ChatMessageStreaming, None]:
|
||||
self.randomize()
|
||||
|
||||
|
@ -11,6 +11,8 @@ from typing import (
|
||||
import inspect
|
||||
import json
|
||||
|
||||
from database.core import RedisDatabase
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
from models import (
|
||||
ApiActivityType,
|
||||
@ -153,7 +155,14 @@ Avoid vague categorizations and be precise about whether skills are explicitly r
|
||||
return display
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ApiMessage, None]:
|
||||
if not self.user:
|
||||
error_message = ChatMessageError(session_id=session_id, content="User is not set for this agent.")
|
||||
|
@ -1,23 +1,31 @@
|
||||
from __future__ import annotations
|
||||
from typing import Literal, AsyncGenerator, ClassVar, Optional, Any
|
||||
|
||||
from database.core import RedisDatabase
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
from logger import logger
|
||||
|
||||
from .registry import agent_registry
|
||||
from models import ApiMessage, ApiStatusType, ChatMessageError, ChatMessageRagSearch, ApiStatusType, Tunables
|
||||
from models import ApiMessage, ApiStatusType, ChatMessageError, ChatMessageRagSearch, Tunables
|
||||
|
||||
|
||||
class Chat(Agent):
|
||||
class RagSearchChat(Agent):
|
||||
"""
|
||||
Chat Agent
|
||||
RagSearchChat Agent
|
||||
"""
|
||||
|
||||
agent_type: Literal["rag_search"] = "rag_search" # type: ignore
|
||||
_agent_type: ClassVar[str] = agent_type # Add this for registration
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ApiMessage, None]:
|
||||
"""
|
||||
Generate a response based on the user message and the provided LLM.
|
||||
@ -53,4 +61,4 @@ class Chat(Agent):
|
||||
|
||||
|
||||
# Register the base agent
|
||||
agent_registry.register(Chat._agent_type, Chat)
|
||||
agent_registry.register(RagSearchChat._agent_type, RagSearchChat)
|
||||
|
@ -9,6 +9,8 @@ from typing import (
|
||||
) # NOTE: You must import Optional for late binding to work
|
||||
import json
|
||||
|
||||
from database.core import RedisDatabase
|
||||
|
||||
from .base import Agent, agent_registry
|
||||
from models import (
|
||||
ApiMessage,
|
||||
@ -106,7 +108,14 @@ JSON RESPONSE:"""
|
||||
return system_prompt, prompt
|
||||
|
||||
async def generate(
|
||||
self, llm: Any, model: str, session_id: str, prompt: str, tunables: Optional[Tunables] = None, temperature=0.7
|
||||
self,
|
||||
llm: Any,
|
||||
model: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
database: RedisDatabase,
|
||||
tunables: Optional[Tunables] = None,
|
||||
temperature=0.7,
|
||||
) -> AsyncGenerator[ApiMessage, None]:
|
||||
if not self.user:
|
||||
error_message = ChatMessageError(
|
||||
|
@ -1084,11 +1084,6 @@ class ChatMessageUser(ApiMessage):
|
||||
class ChatMessage(ChatMessageUser):
|
||||
role: ChatSenderType = ChatSenderType.ASSISTANT
|
||||
metadata: ChatMessageMetaData = Field(default=ChatMessageMetaData())
|
||||
# attachments: Optional[List[Attachment]] = None
|
||||
# reactions: Optional[List[MessageReaction]] = None
|
||||
# is_edited: bool = Field(False, alias=str("isEdited"))
|
||||
# edit_history: Optional[List[EditHistory]] = Field(default=None, alias=str("editHistory"))
|
||||
|
||||
|
||||
class ChatMessageSkillAssessment(ChatMessageUser):
|
||||
role: ChatSenderType = ChatSenderType.ASSISTANT
|
||||
@ -1145,7 +1140,6 @@ class ChatSession(BaseModel):
|
||||
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("lastActivity"))
|
||||
title: Optional[str] = None
|
||||
context: ChatContext
|
||||
# messages: Optional[List[ChatMessage]] = None
|
||||
is_archived: bool = Field(False, alias=str("isArchived"))
|
||||
system_prompt: Optional[str] = Field(default=None, alias=str("systemPrompt"))
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
@ -299,7 +299,7 @@ class ChromaDBFileWatcher(FileSystemEventHandler):
|
||||
n_components=2,
|
||||
random_state=8911,
|
||||
metric="cosine",
|
||||
n_neighbors=30,
|
||||
n_neighbors=round(min(30, len(self._umap_collection.embeddings) * 0.5)),
|
||||
min_dist=0.1,
|
||||
)
|
||||
self._umap_embedding_2d = self._umap_model_2d.fit_transform(vectors) # type: ignore
|
||||
@ -312,7 +312,7 @@ class ChromaDBFileWatcher(FileSystemEventHandler):
|
||||
n_components=3,
|
||||
random_state=8911,
|
||||
metric="cosine",
|
||||
n_neighbors=30,
|
||||
n_neighbors=round(min(30, len(self._umap_collection.embeddings) * 0.5)),
|
||||
min_dist=0.01,
|
||||
)
|
||||
self._umap_embedding_3d = self._umap_model_3d.fit_transform(vectors) # type: ignore
|
||||
|
@ -113,6 +113,7 @@ async def create_candidate_ai(
|
||||
model=defines.model,
|
||||
session_id=user_message.session_id,
|
||||
prompt=user_message.content,
|
||||
database=database,
|
||||
):
|
||||
if isinstance(generated_message, ChatMessageError):
|
||||
error_message: ChatMessageError = generated_message
|
||||
@ -1375,6 +1376,7 @@ async def post_candidate_rag_search(
|
||||
model=defines.model,
|
||||
session_id=user_message.session_id,
|
||||
prompt=user_message.content,
|
||||
database=database,
|
||||
):
|
||||
rag_message = generated_message
|
||||
|
||||
@ -1387,6 +1389,7 @@ async def post_candidate_rag_search(
|
||||
return create_success_response(final_message.content[0].model_dump(by_alias=True))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(backstory_traceback.format_exc())
|
||||
logger.error(f"❌ Get candidate chat summary error: {e}")
|
||||
return JSONResponse(status_code=500, content=create_error_response("SUMMARY_ERROR", str(e)))
|
||||
|
||||
@ -1622,6 +1625,7 @@ async def get_candidate_skill_match(
|
||||
model=defines.model,
|
||||
session_id=MOCK_UUID,
|
||||
prompt=skill,
|
||||
database=database,
|
||||
):
|
||||
if generated_message.status == ApiStatusType.ERROR:
|
||||
if isinstance(generated_message, ChatMessageError):
|
||||
@ -2077,9 +2081,6 @@ async def get_candidate_chat_sessions(
|
||||
session = ChatSession.model_validate(session_data)
|
||||
if session.user_id != current_user.id:
|
||||
# User can only access their own sessions
|
||||
logger.info(
|
||||
f"🔗 Skipping session {session.id} - not owned by user {current_user.id} (created by {session.user_id})"
|
||||
)
|
||||
continue
|
||||
# Check if this session is related to the candidate
|
||||
context = session.context
|
||||
|
@ -171,7 +171,7 @@ async def post_chat_session_message_stream(
|
||||
# Get candidate info if this chat is about a specific candidate
|
||||
if candidate_info:
|
||||
logger.info(
|
||||
f"🔗 Chat session {user_message.session_id} about candidate {candidate_info['name']} accessed by user {current_user.id}"
|
||||
f"🔗 Chat session {user_message.session_id} about {candidate_info['name']} accessed by user {current_user.id}"
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
@ -208,19 +208,15 @@ async def post_chat_session_message_stream(
|
||||
content=create_error_response("AGENT_NOT_FOUND", "No agent found for this chat type"),
|
||||
)
|
||||
|
||||
# Persist user message to database
|
||||
await database.add_chat_message(user_message.session_id, user_message.model_dump())
|
||||
logger.info(f"💬 User message saved to database for session {user_message.session_id}")
|
||||
|
||||
# Update session last activity
|
||||
chat_session_data["lastActivity"] = datetime.now(UTC).isoformat()
|
||||
await database.set_chat_session(user_message.session_id, chat_session_data)
|
||||
chat_session.last_activity = datetime.now(UTC)
|
||||
await database.set_chat_session(user_message.session_id, chat_session.model_dump())
|
||||
|
||||
return await stream_agent_response(
|
||||
chat_agent=chat_agent,
|
||||
user_message=user_message,
|
||||
database=database,
|
||||
chat_session_data=chat_session_data,
|
||||
chat_session=chat_session,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
|
@ -136,6 +136,7 @@ async def create_job_from_content(database: RedisDatabase, current_user: Candida
|
||||
model=defines.model,
|
||||
session_id=MOCK_UUID,
|
||||
prompt=markdown_message.content,
|
||||
database=database,
|
||||
):
|
||||
if message.status != ApiStatusType.DONE:
|
||||
yield message
|
||||
|
@ -12,7 +12,7 @@ from fastapi.responses import StreamingResponse
|
||||
|
||||
import defines
|
||||
from logger import logger
|
||||
from models import DocumentType
|
||||
from models import ChatSession, DocumentType
|
||||
from models import Job, ChatMessage, ApiStatusType
|
||||
|
||||
import utils.llm_proxy as llm_manager
|
||||
@ -65,7 +65,9 @@ def filter_and_paginate(
|
||||
return paginated_items, total
|
||||
|
||||
|
||||
async def stream_agent_response(chat_agent, user_message, database, chat_session_data=None) -> StreamingResponse:
|
||||
async def stream_agent_response(
|
||||
chat_agent, user_message, database, chat_session: Optional[ChatSession] = None
|
||||
) -> StreamingResponse:
|
||||
"""Stream agent response with proper formatting"""
|
||||
|
||||
async def message_stream_generator():
|
||||
@ -79,6 +81,7 @@ async def stream_agent_response(chat_agent, user_message, database, chat_session
|
||||
model=defines.model,
|
||||
session_id=user_message.session_id,
|
||||
prompt=user_message.content,
|
||||
database=database,
|
||||
):
|
||||
if generated_message.status == ApiStatusType.ERROR:
|
||||
logger.error(f"❌ AI generation error: {generated_message.content}")
|
||||
@ -109,13 +112,10 @@ async def stream_agent_response(chat_agent, user_message, database, chat_session
|
||||
# After streaming is complete, persist the final AI message to database
|
||||
if final_message and final_message.status == ApiStatusType.DONE:
|
||||
try:
|
||||
if database and chat_session_data:
|
||||
await database.add_chat_message(final_message.session_id, final_message.model_dump())
|
||||
logger.info(f"🤖 Message saved to database for session {final_message.session_id}")
|
||||
|
||||
if database and chat_session:
|
||||
# Update session last activity again
|
||||
chat_session_data["lastActivity"] = datetime.now(UTC).isoformat()
|
||||
await database.set_chat_session(final_message.session_id, chat_session_data)
|
||||
chat_session.last_activity = datetime.now(UTC)
|
||||
await database.set_chat_session(final_message.session_id, chat_session.model_dump())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to save message to database: {e}")
|
||||
@ -271,6 +271,7 @@ async def create_job_from_content(database, current_user, content: str):
|
||||
model=defines.model,
|
||||
session_id=MOCK_UUID,
|
||||
prompt=markdown_message.content,
|
||||
database=database,
|
||||
):
|
||||
if message.status != ApiStatusType.DONE:
|
||||
yield message
|
||||
|
Loading…
x
Reference in New Issue
Block a user