From 8a4f94817ac7cb9307c69118d7287962dc1a1903 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Wed, 30 Apr 2025 23:00:16 -0700 Subject: [PATCH] Working! --- frontend/src/Conversation.tsx | 30 +++++++----- frontend/src/Message.tsx | 80 +++++++++++++------------------ frontend/src/VectorVisualizer.tsx | 6 ++- src/server.py | 5 ++ src/utils/agents/__init__.py | 43 +++++++++++++++++ src/utils/agents/chat.py | 4 +- src/utils/agents/types.py | 38 +++++++++++++++ src/utils/rag.py | 8 +++- src/utils/setup_logging.py | 32 +++++++++++++ 9 files changed, 179 insertions(+), 67 deletions(-) create mode 100644 src/utils/agents/__init__.py create mode 100644 src/utils/agents/types.py create mode 100644 src/utils/setup_logging.py diff --git a/frontend/src/Conversation.tsx b/frontend/src/Conversation.tsx index 9992fb4..2b21947 100644 --- a/frontend/src/Conversation.tsx +++ b/frontend/src/Conversation.tsx @@ -138,6 +138,7 @@ const Conversation = forwardRef(({ 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 */ @@ -402,7 +403,6 @@ const Conversation = forwardRef(({ try { const update = JSON.parse(line); - console.log('Parsed update:', update.response); switch (update.status) { case 'processing': @@ -414,22 +414,24 @@ const Conversation = forwardRef(({ await new Promise(resolve => setTimeout(resolve, 0)); break; case 'done': + console.log('Done processing:', update); // Replace processing message with final result if (onResponse) { - update.message = onResponse(update.response); + update.message = onResponse(update); } setProcessingMessage(undefined); - const backstoryMessage: BackstoryMessage = update.response; + const backstoryMessage: BackstoryMessage = update; setConversation([ ...conversationRef.current, { - role: 'user', - content: backstoryMessage.prompt || "", - }, { + // role: 'user', + // content: backstoryMessage.prompt || "", + // }, { role: 'assistant', + origin: type, + content: backstoryMessage.response || "", prompt: backstoryMessage.prompt || "", preamble: backstoryMessage.preamble || {}, full_content: backstoryMessage.full_content || "", - content: backstoryMessage.response || "", metadata: backstoryMessage.metadata, actions: backstoryMessage.actions, }] as MessageList); @@ -485,21 +487,23 @@ const Conversation = forwardRef(({ }, 5000); break; case 'done': + console.log('Done processing:', update); if (onResponse) { - update.message = onResponse(update.message); + update.message = onResponse(update); } setProcessingMessage(undefined); - const backstoryMessage: BackstoryMessage = update.message; + const backstoryMessage: BackstoryMessage = update; setConversation([ ...conversationRef.current, { - role: 'user', - content: backstoryMessage.prompt || "", - }, { + // role: 'user', + // content: backstoryMessage.prompt || "", + // }, { role: 'assistant', + origin: type, prompt: backstoryMessage.prompt || "", + content: backstoryMessage.response || "", preamble: backstoryMessage.preamble || {}, full_content: backstoryMessage.full_content || "", - content: backstoryMessage.response || "", metadata: backstoryMessage.metadata, actions: backstoryMessage.actions, }] as MessageList); diff --git a/frontend/src/Message.tsx b/frontend/src/Message.tsx index 61a2252..0cc8fd8 100644 --- a/frontend/src/Message.tsx +++ b/frontend/src/Message.tsx @@ -19,6 +19,7 @@ import Typography from '@mui/material/Typography'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { ExpandMore } from './ExpandMore'; import { SxProps, Theme } from '@mui/material'; +import JsonView from '@uiw/react-json-view'; import { ChatBubble } from './ChatBubble'; import { StyledMarkdown } from './StyledMarkdown'; @@ -182,7 +183,7 @@ const MessageMeta = (props: MessageMetaProps) => { } { - rag.map((rag: any) => (<> + rag.map((rag: any) => ( }> @@ -190,6 +191,10 @@ const MessageMeta = (props: MessageMetaProps) => { + + UMAP Vector Visualization of '{rag.name}' RAG + + {rag.ids.map((id: number, index: number) => {index !== 0 && } @@ -205,54 +210,33 @@ const MessageMeta = (props: MessageMetaProps) => { )} - - }> - - UMAP Vector Visualization of RAG - - - - - - - - }> - - All response fields - - - - {Object.entries(message) - .filter(([key, value]) => key !== undefined && value !== undefined) - .map(([key, value]) => (typeof (value) !== "string" || value?.trim() !== "") && - - }> - {key} - - - {key === "metadata" && - Object.entries(value) - .filter(([key, value]) => key !== undefined && value !== undefined) - .map(([key, value]) => ( - - }> - {key} - - -
{`${typeof (value) !== "object" ? value : JSON.stringify(value)}`}
-
-
- ))} - {key !== "metadata" && -
{typeof (value) !== "object" ? value : JSON.stringify(value)}
- } -
-
- )} -
-
- )) + )) } + + + }> + + All response fields + + + + {Object.entries(message) + .filter(([key, value]) => key !== undefined && value !== undefined) + .map(([key, value]) => (typeof (value) !== "string" || value?.trim() !== "") && + + }> + {key} + + + {typeof (value) === "string" ? +
{value}
: + + } +
+
+ )} +
+
); }; diff --git a/frontend/src/VectorVisualizer.tsx b/frontend/src/VectorVisualizer.tsx index ab3e7a8..e7d38e8 100644 --- a/frontend/src/VectorVisualizer.tsx +++ b/frontend/src/VectorVisualizer.tsx @@ -82,6 +82,7 @@ const emojiMap: Record = { query: '🔍', resume: '📄', projects: '📁', + jobs: '📁', 'performance-reviews': '📄', news: '📰', }; @@ -91,7 +92,8 @@ const colorMap: Record = { resume: '#4A7A7D', // Dusty Teal — secondary theme color projects: '#1A2536', // Midnight Blue — rich and deep 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 = { @@ -156,7 +158,7 @@ const VectorVisualizer: React.FC = (props: VectorVisualiz useEffect(() => { if (!result || !result.embeddings) return; if (result.embeddings.length === 0) return; - + console.log('Result:', result); const vectors: (number[])[] = [...result.embeddings]; const documents = [...result.documents || []]; const metadatas = [...result.metadatas || []]; diff --git a/src/server.py b/src/server.py index 55ec946..7ae395a 100644 --- a/src/server.py +++ b/src/server.py @@ -413,6 +413,8 @@ class WebServer: dimensions = data.get("dimensions", 2) result = self.file_watcher.umap_collection + if not result: + return JSONResponse({"error": "No UMAP collection found"}, status_code=404) if dimensions == 2: logger.info("Returning 2D UMAP") umap_embedding = self.file_watcher.umap_embedding_2d @@ -420,6 +422,9 @@ class WebServer: logger.info("Returning 3D UMAP") 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() return JSONResponse(result) diff --git a/src/utils/agents/__init__.py b/src/utils/agents/__init__.py new file mode 100644 index 0000000..88d3c3b --- /dev/null +++ b/src/utils/agents/__init__.py @@ -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") \ No newline at end of file diff --git a/src/utils/agents/chat.py b/src/utils/agents/chat.py index b2acd7c..43a563c 100644 --- a/src/utils/agents/chat.py +++ b/src/utils/agents/chat.py @@ -203,9 +203,9 @@ class Chat(Agent, ABC): if not self.context: 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()): - 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}" # Estimate token length of new messages diff --git a/src/utils/agents/types.py b/src/utils/agents/types.py new file mode 100644 index 0000000..8938805 --- /dev/null +++ b/src/utils/agents/types.py @@ -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() \ No newline at end of file diff --git a/src/utils/rag.py b/src/utils/rag.py index 9260ef7..76c2db5 100644 --- a/src/utils/rag.py +++ b/src/utils/rag.py @@ -52,8 +52,12 @@ class ChromaDBFileWatcher(FileSystemEventHandler): self.chunk_size = chunk_size self.chunk_overlap = chunk_overlap 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.embedding_model = SentenceTransformer('all-MiniLM-L6-v2') diff --git a/src/utils/setup_logging.py b/src/utils/setup_logging.py new file mode 100644 index 0000000..5428295 --- /dev/null +++ b/src/utils/setup_logging.py @@ -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