This commit is contained in:
James Ketr 2025-04-30 23:00:16 -07:00
parent 10f28b0e9b
commit 8a4f94817a
9 changed files with 179 additions and 67 deletions

View File

@ -138,6 +138,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
let filtered = []; let filtered = [];
if (messageFilter === undefined) { if (messageFilter === undefined) {
filtered = conversation; filtered = conversation;
// console.log('No message filter provided. Using all messages.', filtered);
} else { } else {
//console.log('Filtering conversation...') //console.log('Filtering conversation...')
filtered = messageFilter(conversation); /* Do not copy conversation or useEffect will loop forever */ filtered = messageFilter(conversation); /* Do not copy conversation or useEffect will loop forever */
@ -402,7 +403,6 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
try { try {
const update = JSON.parse(line); const update = JSON.parse(line);
console.log('Parsed update:', update.response);
switch (update.status) { switch (update.status) {
case 'processing': case 'processing':
@ -414,22 +414,24 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
await new Promise(resolve => setTimeout(resolve, 0)); await new Promise(resolve => setTimeout(resolve, 0));
break; break;
case 'done': case 'done':
console.log('Done processing:', update);
// Replace processing message with final result // Replace processing message with final result
if (onResponse) { if (onResponse) {
update.message = onResponse(update.response); update.message = onResponse(update);
} }
setProcessingMessage(undefined); setProcessingMessage(undefined);
const backstoryMessage: BackstoryMessage = update.response; const backstoryMessage: BackstoryMessage = update;
setConversation([ setConversation([
...conversationRef.current, { ...conversationRef.current, {
role: 'user', // role: 'user',
content: backstoryMessage.prompt || "", // content: backstoryMessage.prompt || "",
}, { // }, {
role: 'assistant', role: 'assistant',
origin: type,
content: backstoryMessage.response || "",
prompt: backstoryMessage.prompt || "", prompt: backstoryMessage.prompt || "",
preamble: backstoryMessage.preamble || {}, preamble: backstoryMessage.preamble || {},
full_content: backstoryMessage.full_content || "", full_content: backstoryMessage.full_content || "",
content: backstoryMessage.response || "",
metadata: backstoryMessage.metadata, metadata: backstoryMessage.metadata,
actions: backstoryMessage.actions, actions: backstoryMessage.actions,
}] as MessageList); }] as MessageList);
@ -485,21 +487,23 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
}, 5000); }, 5000);
break; break;
case 'done': case 'done':
console.log('Done processing:', update);
if (onResponse) { if (onResponse) {
update.message = onResponse(update.message); update.message = onResponse(update);
} }
setProcessingMessage(undefined); setProcessingMessage(undefined);
const backstoryMessage: BackstoryMessage = update.message; const backstoryMessage: BackstoryMessage = update;
setConversation([ setConversation([
...conversationRef.current, { ...conversationRef.current, {
role: 'user', // role: 'user',
content: backstoryMessage.prompt || "", // content: backstoryMessage.prompt || "",
}, { // }, {
role: 'assistant', role: 'assistant',
origin: type,
prompt: backstoryMessage.prompt || "", prompt: backstoryMessage.prompt || "",
content: backstoryMessage.response || "",
preamble: backstoryMessage.preamble || {}, preamble: backstoryMessage.preamble || {},
full_content: backstoryMessage.full_content || "", full_content: backstoryMessage.full_content || "",
content: backstoryMessage.response || "",
metadata: backstoryMessage.metadata, metadata: backstoryMessage.metadata,
actions: backstoryMessage.actions, actions: backstoryMessage.actions,
}] as MessageList); }] as MessageList);

View File

@ -19,6 +19,7 @@ import Typography from '@mui/material/Typography';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { ExpandMore } from './ExpandMore'; import { ExpandMore } from './ExpandMore';
import { SxProps, Theme } from '@mui/material'; import { SxProps, Theme } from '@mui/material';
import JsonView from '@uiw/react-json-view';
import { ChatBubble } from './ChatBubble'; import { ChatBubble } from './ChatBubble';
import { StyledMarkdown } from './StyledMarkdown'; import { StyledMarkdown } from './StyledMarkdown';
@ -182,7 +183,7 @@ const MessageMeta = (props: MessageMetaProps) => {
</Accordion> </Accordion>
} }
{ {
rag.map((rag: any) => (<> rag.map((rag: any) => (
<Accordion key={rag.name}> <Accordion key={rag.name}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box sx={{ fontSize: "0.8rem" }}> <Box sx={{ fontSize: "0.8rem" }}>
@ -190,6 +191,10 @@ const MessageMeta = (props: MessageMetaProps) => {
</Box> </Box>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<Box sx={{ fontSize: "0.8rem" }}>
UMAP Vector Visualization of '{rag.name}' RAG
</Box>
<VectorVisualizer inline {...props.messageProps} {...props.metadata} rag={rag} />
{rag.ids.map((id: number, index: number) => <Box key={index}> {rag.ids.map((id: number, index: number) => <Box key={index}>
{index !== 0 && <Divider />} {index !== 0 && <Divider />}
<Box sx={{ fontSize: "0.75rem", display: "flex", flexDirection: "row", mb: 0.5, mt: 0.5 }}> <Box sx={{ fontSize: "0.75rem", display: "flex", flexDirection: "row", mb: 0.5, mt: 0.5 }}>
@ -205,16 +210,9 @@ const MessageMeta = (props: MessageMetaProps) => {
)} )}
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
<Accordion> ))
<AccordionSummary expandIcon={<ExpandMoreIcon />}> }
<Box sx={{ fontSize: "0.8rem" }}>
UMAP Vector Visualization of RAG
</Box>
</AccordionSummary>
<AccordionDetails>
<VectorVisualizer inline {...message} {...props.metadata} rag={rag} />
</AccordionDetails>
</Accordion>
<Accordion> <Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}> <AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box sx={{ fontSize: "0.8rem" }}> <Box sx={{ fontSize: "0.8rem" }}>
@ -230,29 +228,15 @@ const MessageMeta = (props: MessageMetaProps) => {
{key} {key}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
{key === "metadata" && {typeof (value) === "string" ?
Object.entries(value) <pre>{value}</pre> :
.filter(([key, value]) => key !== undefined && value !== undefined) <JsonView collapsed={1} value={value as any} style={{ fontSize: "0.8rem", maxHeight: "20rem", overflow: "auto" }} />
.map(([key, value]) => (
<Accordion key={`metadata.${key}`}>
<AccordionSummary sx={{ fontSize: "1rem", fontWeight: "bold" }} expandIcon={<ExpandMoreIcon />}>
{key}
</AccordionSummary>
<AccordionDetails>
<pre>{`${typeof (value) !== "object" ? value : JSON.stringify(value)}`}</pre>
</AccordionDetails>
</Accordion>
))}
{key !== "metadata" &&
<pre>{typeof (value) !== "object" ? value : JSON.stringify(value)}</pre>
} }
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
)} )}
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
</>))
}
</>); </>);
}; };

View File

@ -82,6 +82,7 @@ const emojiMap: Record<string, string> = {
query: '🔍', query: '🔍',
resume: '📄', resume: '📄',
projects: '📁', projects: '📁',
jobs: '📁',
'performance-reviews': '📄', 'performance-reviews': '📄',
news: '📰', news: '📰',
}; };
@ -91,7 +92,8 @@ const colorMap: Record<string, string> = {
resume: '#4A7A7D', // Dusty Teal — secondary theme color resume: '#4A7A7D', // Dusty Teal — secondary theme color
projects: '#1A2536', // Midnight Blue — rich and deep projects: '#1A2536', // Midnight Blue — rich and deep
news: '#D3CDBF', // Warm Gray — soft and neutral news: '#D3CDBF', // Warm Gray — soft and neutral
'performance-reviews': '#FF0000', // Bright red 'performance-reviews': '#FFD0D0', // Light red
'jobs': '#F3aD8F', // Warm Gray — soft and neutral
}; };
const sizeMap: Record<string, number> = { const sizeMap: Record<string, number> = {
@ -156,7 +158,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
useEffect(() => { useEffect(() => {
if (!result || !result.embeddings) return; if (!result || !result.embeddings) return;
if (result.embeddings.length === 0) return; if (result.embeddings.length === 0) return;
console.log('Result:', result);
const vectors: (number[])[] = [...result.embeddings]; const vectors: (number[])[] = [...result.embeddings];
const documents = [...result.documents || []]; const documents = [...result.documents || []];
const metadatas = [...result.metadatas || []]; const metadatas = [...result.metadatas || []];

View File

@ -413,6 +413,8 @@ class WebServer:
dimensions = data.get("dimensions", 2) dimensions = data.get("dimensions", 2)
result = self.file_watcher.umap_collection result = self.file_watcher.umap_collection
if not result:
return JSONResponse({"error": "No UMAP collection found"}, status_code=404)
if dimensions == 2: if dimensions == 2:
logger.info("Returning 2D UMAP") logger.info("Returning 2D UMAP")
umap_embedding = self.file_watcher.umap_embedding_2d umap_embedding = self.file_watcher.umap_embedding_2d
@ -420,6 +422,9 @@ class WebServer:
logger.info("Returning 3D UMAP") logger.info("Returning 3D UMAP")
umap_embedding = self.file_watcher.umap_embedding_3d umap_embedding = self.file_watcher.umap_embedding_3d
if len(umap_embedding) == 0:
return JSONResponse({"error": "No UMAP embedding found"}, status_code=404)
result["embeddings"] = umap_embedding.tolist() result["embeddings"] = umap_embedding.tolist()
return JSONResponse(result) return JSONResponse(result)

View File

@ -0,0 +1,43 @@
from __future__ import annotations
import importlib
import pathlib
import inspect
import logging
from typing import TypeAlias, Dict, Tuple
from pydantic import BaseModel
from . base import Agent
# Type alias for Agent or any subclass
AnyAgent: TypeAlias = Agent # BaseModel covers Agent and subclasses
package_dir = pathlib.Path(__file__).parent
package_name = __name__
__all__ = []
class_registry: Dict[str, Tuple[str, str]] = {} # Maps class_name to (module_name, class_name)
for path in package_dir.glob("*.py"):
if path.name in ("__init__.py", "base.py") or path.name.startswith("_"):
continue
module_name = path.stem
full_module_name = f"{package_name}.{module_name}"
try:
module = importlib.import_module(full_module_name)
# Find all Agent subclasses in the module
for name, obj in inspect.getmembers(module, inspect.isclass):
if (
issubclass(obj, AnyAgent)
and obj is not AnyAgent
and obj is not Agent
and name not in class_registry
):
class_registry[name] = (full_module_name, name)
globals()[name] = obj
logging.info(f"Adding agent: {name} from {full_module_name}")
__all__.append(name)
except ImportError as e:
logging.error(f"Failed to import module {full_module_name}: {e}")
__all__.append("AnyAgent")

View File

@ -203,9 +203,9 @@ class Chat(Agent, ABC):
if not self.context: if not self.context:
raise ValueError("Context is not set for this agent.") raise ValueError("Context is not set for this agent.")
message.full_content = "" message.full_content = f"<|system|>{self.system_prompt.strip()}\n"
for i, p in enumerate(message.preamble.keys()): for i, p in enumerate(message.preamble.keys()):
message.full_content += '' if i == 0 else '\n\n' + f"<|{p}|>{message.preamble[p].strip()}\n" message.full_content += f"\n<|{p}|>\n{message.preamble[p].strip()}\n"
message.full_content += f"{message.prompt}" message.full_content += f"{message.prompt}"
# Estimate token length of new messages # Estimate token length of new messages

38
src/utils/agents/types.py Normal file
View File

@ -0,0 +1,38 @@
from __future__ import annotations
from typing import List, Dict, Any, Union, ForwardRef, TypeVar, Optional, TYPE_CHECKING, Type, ClassVar, Literal
from typing_extensions import Annotated
from pydantic import Field, BaseModel
from abc import ABC, abstractmethod
# Forward references
AgentRef = ForwardRef('Agent')
ContextRef = ForwardRef('Context')
# We'll use a registry pattern rather than hardcoded strings
class AgentRegistry:
"""Registry for agent types and classes"""
_registry: Dict[str, Type] = {}
@classmethod
def register(cls, agent_type: str, agent_class: Type) -> Type:
"""Register an agent class with its type"""
cls._registry[agent_type] = agent_class
return agent_class
@classmethod
def get_class(cls, agent_type: str) -> Optional[Type]:
"""Get the class for a given agent type"""
return cls._registry.get(agent_type)
@classmethod
def get_types(cls) -> List[str]:
"""Get all registered agent types"""
return list(cls._registry.keys())
@classmethod
def get_classes(cls) -> Dict[str, Type]:
"""Get all registered agent classes"""
return cls._registry.copy()
# Create a singleton instance
registry = AgentRegistry()

View File

@ -52,8 +52,12 @@ class ChromaDBFileWatcher(FileSystemEventHandler):
self.chunk_size = chunk_size self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap self.chunk_overlap = chunk_overlap
self.loop = loop self.loop = loop
self._umap_collection = None
self._umap_embedding_2d = []
self._umap_embedding_3d = []
self._umap_model_2d = None
self._umap_model_3d = None
self._collection = None
self.md = MarkItDown(enable_plugins=False) # Set to True to enable plugins self.md = MarkItDown(enable_plugins=False) # Set to True to enable plugins
#self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2') #self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')

View File

@ -0,0 +1,32 @@
import os
import warnings
import logging
from . import defines
def setup_logging(level=defines.logging_level) -> logging.Logger:
os.environ["TORCH_CPP_LOG_LEVEL"] = "ERROR"
warnings.filterwarnings("ignore", message="Overriding a previously registered kernel")
warnings.filterwarnings("ignore", message="Warning only once for all operators")
warnings.filterwarnings("ignore", message="Couldn't find ffmpeg or avconv")
warnings.filterwarnings("ignore", message="'force_all_finite' was renamed to")
warnings.filterwarnings("ignore", message="n_jobs value 1 overridden")
numeric_level = getattr(logging, level.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError(f"Invalid log level: {level}")
logging.basicConfig(
level=numeric_level,
format="%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
force=True
)
# Now reduce verbosity for FastAPI, Uvicorn, Starlette
for noisy_logger in ("uvicorn", "uvicorn.error", "uvicorn.access", "fastapi", "starlette"):
logging.getLogger(noisy_logger).setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
return logger