Commit ·
9d5041f
1
Parent(s): 523ccd6
refactor application structure and enhance logging capabilities
Browse files- Introduced a new logging configuration for better traceability across components.
- Added a centralized logging setup in the config module.
- Implemented logging in various application components including agent, memory, and UI.
- Created new modules for knowledge base, tools, and visualizer functionalities.
- Updated .gitignore to include log files and added a new src directory for better organization.
- Removed deprecated memory management code and refactored conversation memory handling.
- Enhanced the Context Engineering Visualizer with improved context tracking and visualization features.
- .gitignore +4 -0
- config/__init__.py +0 -5
- main.py +11 -0
- src/__init__.py +3 -0
- {app → src/app}/__init__.py +0 -0
- {app → src/app}/agent.py +40 -4
- {app → src/app}/knowledge.py +70 -19
- {app → src/app}/memory.py +21 -2
- {app → src/app}/tools.py +23 -6
- {app → src/app}/ui.py +25 -1
- {app → src/app}/visualizer.py +0 -0
- src/config/__init__.py +15 -0
- src/config/logging_config.py +77 -0
- {config → src/config}/settings.py +2 -2
- src/utils/__init__.py +1 -0
- {app → src/utils}/process_knowledge.py +1 -1
- uv.lock +3 -3
.gitignore
CHANGED
|
@@ -12,5 +12,9 @@ wheels/
|
|
| 12 |
# Secrets
|
| 13 |
.env
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
# Reference files
|
| 16 |
references/
|
|
|
|
| 12 |
# Secrets
|
| 13 |
.env
|
| 14 |
|
| 15 |
+
# Log files
|
| 16 |
+
logs/
|
| 17 |
+
*.log
|
| 18 |
+
|
| 19 |
# Reference files
|
| 20 |
references/
|
config/__init__.py
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
"""Configuration module for Context Engineering Visualizer"""
|
| 2 |
-
|
| 3 |
-
from .settings import Settings
|
| 4 |
-
|
| 5 |
-
__all__ = ["Settings"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main.py
CHANGED
|
@@ -3,7 +3,18 @@ Context Engineering Visualizer
|
|
| 3 |
Main entry point for the application
|
| 4 |
"""
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
from app.ui import launch_ui
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
if __name__ == "__main__":
|
|
|
|
| 9 |
launch_ui()
|
|
|
|
| 3 |
Main entry point for the application
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# Add src directory to path
|
| 10 |
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src'))
|
| 11 |
+
|
| 12 |
+
from config import setup_logger
|
| 13 |
from app.ui import launch_ui
|
| 14 |
|
| 15 |
+
# Setup application logger - this initializes handlers
|
| 16 |
+
logger = setup_logger("context_visualizer")
|
| 17 |
+
|
| 18 |
if __name__ == "__main__":
|
| 19 |
+
logger.info("Starting Context Engineering Visualizer")
|
| 20 |
launch_ui()
|
src/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Context Engineering Visualizer package"""
|
| 2 |
+
|
| 3 |
+
__version__ = "1.0.0"
|
{app → src/app}/__init__.py
RENAMED
|
File without changes
|
{app → src/app}/agent.py
RENAMED
|
@@ -7,7 +7,8 @@ from .visualizer import ContextVisualizer
|
|
| 7 |
from .memory import ConversationMemory
|
| 8 |
from .knowledge import KnowledgeBase
|
| 9 |
from .tools import calculate_metric, get_current_time
|
| 10 |
-
from config import Settings
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
class ContextEngineeringAgent:
|
|
@@ -20,12 +21,16 @@ class ContextEngineeringAgent:
|
|
| 20 |
"""
|
| 21 |
|
| 22 |
def __init__(self):
|
|
|
|
|
|
|
| 23 |
# Initialize components
|
|
|
|
| 24 |
self.llm = ChatOpenAI(
|
| 25 |
model=Settings.MODEL_NAME,
|
| 26 |
temperature=Settings.TEMPERATURE
|
| 27 |
)
|
| 28 |
|
|
|
|
| 29 |
self.knowledge_base = KnowledgeBase(
|
| 30 |
pdf_path=Settings.PDF_PATH,
|
| 31 |
index_path=Settings.FAISS_INDEX_PATH,
|
|
@@ -33,6 +38,8 @@ class ContextEngineeringAgent:
|
|
| 33 |
top_k=Settings.RAG_TOP_K,
|
| 34 |
recreate_index=False # Load existing index by default
|
| 35 |
)
|
|
|
|
|
|
|
| 36 |
self.memory = ConversationMemory(max_messages=Settings.MAX_CONVERSATION_MESSAGES)
|
| 37 |
self.visualizer = ContextVisualizer()
|
| 38 |
|
|
@@ -41,49 +48,64 @@ class ContextEngineeringAgent:
|
|
| 41 |
|
| 42 |
# Create tools
|
| 43 |
self.tools = [calculate_metric, get_current_time]
|
|
|
|
| 44 |
|
| 45 |
# Create agent
|
|
|
|
| 46 |
self.agent = create_agent(
|
| 47 |
model=self.llm,
|
| 48 |
tools=self.tools,
|
| 49 |
system_prompt=self.system_prompt
|
| 50 |
)
|
| 51 |
|
|
|
|
|
|
|
| 52 |
def process_query(self, user_query: str) -> tuple[str, ContextVisualizer]:
|
| 53 |
"""
|
| 54 |
Process a user query with full context engineering
|
| 55 |
Returns: (response, visualizer)
|
| 56 |
"""
|
|
|
|
|
|
|
| 57 |
# Reset visualizer for new query
|
| 58 |
self.visualizer = ContextVisualizer()
|
| 59 |
|
| 60 |
# Layer 1: System Instructions
|
|
|
|
| 61 |
self.visualizer.add_layer(
|
| 62 |
"System Instructions",
|
| 63 |
self.system_prompt,
|
| 64 |
)
|
| 65 |
|
| 66 |
# Layer 2: Conversation History
|
|
|
|
| 67 |
history_text = self.memory.get_history_text()
|
|
|
|
| 68 |
self.visualizer.add_layer(
|
| 69 |
"Conversation History",
|
| 70 |
history_text if history_text != "No previous conversation" else "No previous conversation",
|
| 71 |
)
|
| 72 |
|
| 73 |
# Layer 3: Retrieved Knowledge (RAG)
|
|
|
|
| 74 |
retrieved_context = self.knowledge_base.retrieve_relevant(user_query)
|
|
|
|
|
|
|
|
|
|
| 75 |
self.visualizer.add_layer(
|
| 76 |
"Retrieved Knowledge (RAG)",
|
| 77 |
retrieved_context,
|
| 78 |
)
|
| 79 |
|
| 80 |
# Layer 4: Current User Query
|
|
|
|
| 81 |
self.visualizer.add_layer(
|
| 82 |
"User Query",
|
| 83 |
user_query,
|
| 84 |
)
|
| 85 |
|
| 86 |
# Layer 5: Available Tools
|
|
|
|
| 87 |
tools_context = "\n".join([
|
| 88 |
f"- {tool.name}: {tool.description}" for tool in self.tools
|
| 89 |
])
|
|
@@ -93,6 +115,9 @@ class ContextEngineeringAgent:
|
|
| 93 |
)
|
| 94 |
|
| 95 |
# Build the context structure
|
|
|
|
|
|
|
|
|
|
| 96 |
context_message = f"""Context from Knowledge Base:
|
| 97 |
{retrieved_context}
|
| 98 |
|
|
@@ -103,15 +128,26 @@ Current Question:
|
|
| 103 |
{user_query}"""
|
| 104 |
|
| 105 |
# Invoke agent
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
# Extract response
|
| 111 |
response = result["messages"][-1].content
|
|
|
|
|
|
|
| 112 |
|
| 113 |
# Update conversation memory
|
|
|
|
| 114 |
self.memory.add_user_message(user_query)
|
|
|
|
| 115 |
self.memory.add_ai_message(response)
|
| 116 |
|
|
|
|
| 117 |
return response, self.visualizer
|
|
|
|
| 7 |
from .memory import ConversationMemory
|
| 8 |
from .knowledge import KnowledgeBase
|
| 9 |
from .tools import calculate_metric, get_current_time
|
| 10 |
+
from config.settings import Settings
|
| 11 |
+
from config import logger_agent, logger_memory, logger_knowledge
|
| 12 |
|
| 13 |
|
| 14 |
class ContextEngineeringAgent:
|
|
|
|
| 21 |
"""
|
| 22 |
|
| 23 |
def __init__(self):
|
| 24 |
+
logger_agent.info("Initializing ContextEngineeringAgent")
|
| 25 |
+
|
| 26 |
# Initialize components
|
| 27 |
+
logger_agent.debug(f"Initializing LLM with model: {Settings.MODEL_NAME}")
|
| 28 |
self.llm = ChatOpenAI(
|
| 29 |
model=Settings.MODEL_NAME,
|
| 30 |
temperature=Settings.TEMPERATURE
|
| 31 |
)
|
| 32 |
|
| 33 |
+
logger_agent.info("Loading knowledge base from FAISS index")
|
| 34 |
self.knowledge_base = KnowledgeBase(
|
| 35 |
pdf_path=Settings.PDF_PATH,
|
| 36 |
index_path=Settings.FAISS_INDEX_PATH,
|
|
|
|
| 38 |
top_k=Settings.RAG_TOP_K,
|
| 39 |
recreate_index=False # Load existing index by default
|
| 40 |
)
|
| 41 |
+
|
| 42 |
+
logger_agent.debug(f"Initializing conversation memory (max_messages={Settings.MAX_CONVERSATION_MESSAGES})")
|
| 43 |
self.memory = ConversationMemory(max_messages=Settings.MAX_CONVERSATION_MESSAGES)
|
| 44 |
self.visualizer = ContextVisualizer()
|
| 45 |
|
|
|
|
| 48 |
|
| 49 |
# Create tools
|
| 50 |
self.tools = [calculate_metric, get_current_time]
|
| 51 |
+
logger_agent.debug(f"Created {len(self.tools)} tools: {[t.name for t in self.tools]}")
|
| 52 |
|
| 53 |
# Create agent
|
| 54 |
+
logger_agent.info("Creating LangChain agent with system prompt and tools")
|
| 55 |
self.agent = create_agent(
|
| 56 |
model=self.llm,
|
| 57 |
tools=self.tools,
|
| 58 |
system_prompt=self.system_prompt
|
| 59 |
)
|
| 60 |
|
| 61 |
+
logger_agent.info("ContextEngineeringAgent initialized successfully")
|
| 62 |
+
|
| 63 |
def process_query(self, user_query: str) -> tuple[str, ContextVisualizer]:
|
| 64 |
"""
|
| 65 |
Process a user query with full context engineering
|
| 66 |
Returns: (response, visualizer)
|
| 67 |
"""
|
| 68 |
+
logger_agent.info(f"Processing query: {user_query[:50]}..." if len(user_query) > 50 else f"Processing query: {user_query}")
|
| 69 |
+
|
| 70 |
# Reset visualizer for new query
|
| 71 |
self.visualizer = ContextVisualizer()
|
| 72 |
|
| 73 |
# Layer 1: System Instructions
|
| 74 |
+
logger_agent.debug("Adding layer: System Instructions")
|
| 75 |
self.visualizer.add_layer(
|
| 76 |
"System Instructions",
|
| 77 |
self.system_prompt,
|
| 78 |
)
|
| 79 |
|
| 80 |
# Layer 2: Conversation History
|
| 81 |
+
logger_memory.debug("Retrieving conversation history")
|
| 82 |
history_text = self.memory.get_history_text()
|
| 83 |
+
logger_agent.debug(f"Conversation history length: {len(history_text)} characters")
|
| 84 |
self.visualizer.add_layer(
|
| 85 |
"Conversation History",
|
| 86 |
history_text if history_text != "No previous conversation" else "No previous conversation",
|
| 87 |
)
|
| 88 |
|
| 89 |
# Layer 3: Retrieved Knowledge (RAG)
|
| 90 |
+
logger_knowledge.info(f"Retrieving relevant documents for query")
|
| 91 |
retrieved_context = self.knowledge_base.retrieve_relevant(user_query)
|
| 92 |
+
doc_count = len([c for c in retrieved_context.split("--- Chunk") if c.strip()])
|
| 93 |
+
logger_knowledge.info(f"Retrieved {doc_count} document chunks")
|
| 94 |
+
logger_agent.debug(f"Retrieved context length: {len(retrieved_context)} characters")
|
| 95 |
self.visualizer.add_layer(
|
| 96 |
"Retrieved Knowledge (RAG)",
|
| 97 |
retrieved_context,
|
| 98 |
)
|
| 99 |
|
| 100 |
# Layer 4: Current User Query
|
| 101 |
+
logger_agent.debug("Adding layer: User Query")
|
| 102 |
self.visualizer.add_layer(
|
| 103 |
"User Query",
|
| 104 |
user_query,
|
| 105 |
)
|
| 106 |
|
| 107 |
# Layer 5: Available Tools
|
| 108 |
+
logger_agent.debug("Adding layer: Available Tools")
|
| 109 |
tools_context = "\n".join([
|
| 110 |
f"- {tool.name}: {tool.description}" for tool in self.tools
|
| 111 |
])
|
|
|
|
| 115 |
)
|
| 116 |
|
| 117 |
# Build the context structure
|
| 118 |
+
total_tokens = sum(self.visualizer.token_counts.values())
|
| 119 |
+
logger_agent.info(f"Total context size: {total_tokens} tokens across {len(self.visualizer.context_layers)} layers")
|
| 120 |
+
|
| 121 |
context_message = f"""Context from Knowledge Base:
|
| 122 |
{retrieved_context}
|
| 123 |
|
|
|
|
| 128 |
{user_query}"""
|
| 129 |
|
| 130 |
# Invoke agent
|
| 131 |
+
logger_agent.info("Invoking LangChain agent with assembled context")
|
| 132 |
+
try:
|
| 133 |
+
result = self.agent.invoke({
|
| 134 |
+
"messages": [{"role": "user", "content": context_message}]
|
| 135 |
+
})
|
| 136 |
+
logger_agent.info("Agent invocation successful")
|
| 137 |
+
except Exception as e:
|
| 138 |
+
logger_agent.error(f"Agent invocation failed: {str(e)}")
|
| 139 |
+
raise
|
| 140 |
|
| 141 |
# Extract response
|
| 142 |
response = result["messages"][-1].content
|
| 143 |
+
response_preview = response[:100] + "..." if len(response) > 100 else response
|
| 144 |
+
logger_agent.info(f"Generated response: {response_preview}")
|
| 145 |
|
| 146 |
# Update conversation memory
|
| 147 |
+
logger_memory.info("Adding user message to conversation memory")
|
| 148 |
self.memory.add_user_message(user_query)
|
| 149 |
+
logger_memory.info("Adding AI response to conversation memory")
|
| 150 |
self.memory.add_ai_message(response)
|
| 151 |
|
| 152 |
+
logger_agent.info("Query processing completed successfully")
|
| 153 |
return response, self.visualizer
|
{app → src/app}/knowledge.py
RENAMED
|
@@ -7,6 +7,7 @@ from langchain_community.vectorstores import FAISS
|
|
| 7 |
from langchain_community.document_loaders import PyPDFLoader
|
| 8 |
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 9 |
from langchain_core.documents import Document
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
class KnowledgeBase:
|
|
@@ -26,6 +27,12 @@ class KnowledgeBase:
|
|
| 26 |
self.pdf_path = pdf_path
|
| 27 |
self.index_path = index_path
|
| 28 |
self.top_k = top_k
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
self.embeddings = OpenAIEmbeddings(model=embedding_model)
|
| 30 |
self.vectorstore = self._load_or_create_index(recreate_index)
|
| 31 |
|
|
@@ -33,34 +40,48 @@ class KnowledgeBase:
|
|
| 33 |
"""Load existing FAISS index or create new one from PDF"""
|
| 34 |
# If index exists and not recreating, load it
|
| 35 |
if not recreate and os.path.exists(self.index_path):
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
# Otherwise, create new index
|
| 44 |
-
|
| 45 |
|
| 46 |
# Remove old index if recreating
|
| 47 |
if recreate and os.path.exists(self.index_path):
|
| 48 |
import shutil
|
| 49 |
try:
|
| 50 |
shutil.rmtree(self.index_path)
|
| 51 |
-
|
| 52 |
except Exception as e:
|
| 53 |
-
|
| 54 |
|
| 55 |
# Load PDF document
|
| 56 |
if not os.path.exists(self.pdf_path):
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
# Split documents into chunks using RecursiveCharacterTextSplitter
|
|
|
|
| 64 |
text_splitter = RecursiveCharacterTextSplitter(
|
| 65 |
chunk_size=800, # Optimized for better granularity
|
| 66 |
chunk_overlap=150, # Reduced proportionally
|
|
@@ -68,14 +89,24 @@ class KnowledgeBase:
|
|
| 68 |
separators=["\n\n", "\n", ". ", ", ", " ", ""] # Paragraph > Line > Sentence > Clause > Word
|
| 69 |
)
|
| 70 |
chunks = text_splitter.split_documents(documents)
|
| 71 |
-
|
| 72 |
|
| 73 |
# Create FAISS index from chunks
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
# Save the index
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
return vectorstore
|
| 81 |
|
|
@@ -91,11 +122,19 @@ class KnowledgeBase:
|
|
| 91 |
List of relevant document chunks
|
| 92 |
"""
|
| 93 |
if not self.vectorstore:
|
| 94 |
-
|
| 95 |
return []
|
| 96 |
|
| 97 |
k = k or self.top_k
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
def retrieve_relevant(self, query: str, k: int = None) -> str:
|
| 101 |
"""
|
|
@@ -108,8 +147,14 @@ class KnowledgeBase:
|
|
| 108 |
Returns:
|
| 109 |
Concatenated text from relevant documents with metadata
|
| 110 |
"""
|
|
|
|
|
|
|
| 111 |
docs = self.retrieve_relevant_docs(query, k)
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
formatted_chunks = []
|
| 114 |
for i, doc in enumerate(docs, 1):
|
| 115 |
chunk_text = f"--- Chunk {i} ---"
|
|
@@ -118,9 +163,15 @@ class KnowledgeBase:
|
|
| 118 |
if doc.metadata:
|
| 119 |
metadata_str = ", ".join([f"{k}: {v}" for k, v in doc.metadata.items()])
|
| 120 |
chunk_text += f"\nMetadata: {metadata_str}"
|
|
|
|
| 121 |
|
| 122 |
# Add content
|
|
|
|
|
|
|
| 123 |
chunk_text += f"\n\n{doc.page_content}"
|
| 124 |
formatted_chunks.append(chunk_text)
|
| 125 |
|
|
|
|
|
|
|
|
|
|
| 126 |
return "\n\n".join(formatted_chunks)
|
|
|
|
| 7 |
from langchain_community.document_loaders import PyPDFLoader
|
| 8 |
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 9 |
from langchain_core.documents import Document
|
| 10 |
+
from config import logger_knowledge
|
| 11 |
|
| 12 |
|
| 13 |
class KnowledgeBase:
|
|
|
|
| 27 |
self.pdf_path = pdf_path
|
| 28 |
self.index_path = index_path
|
| 29 |
self.top_k = top_k
|
| 30 |
+
|
| 31 |
+
logger_knowledge.info(f"Initializing KnowledgeBase with embedding_model={embedding_model}, top_k={top_k}")
|
| 32 |
+
logger_knowledge.debug(f"PDF path: {pdf_path}")
|
| 33 |
+
logger_knowledge.debug(f"Index path: {index_path}")
|
| 34 |
+
|
| 35 |
+
logger_knowledge.info(f"Loading OpenAI embeddings model: {embedding_model}")
|
| 36 |
self.embeddings = OpenAIEmbeddings(model=embedding_model)
|
| 37 |
self.vectorstore = self._load_or_create_index(recreate_index)
|
| 38 |
|
|
|
|
| 40 |
"""Load existing FAISS index or create new one from PDF"""
|
| 41 |
# If index exists and not recreating, load it
|
| 42 |
if not recreate and os.path.exists(self.index_path):
|
| 43 |
+
logger_knowledge.info(f"Loading existing FAISS index from {self.index_path}")
|
| 44 |
+
try:
|
| 45 |
+
vectorstore = FAISS.load_local(
|
| 46 |
+
self.index_path,
|
| 47 |
+
self.embeddings,
|
| 48 |
+
allow_dangerous_deserialization=True
|
| 49 |
+
)
|
| 50 |
+
logger_knowledge.info("FAISS index loaded successfully")
|
| 51 |
+
return vectorstore
|
| 52 |
+
except Exception as e:
|
| 53 |
+
logger_knowledge.error(f"Failed to load FAISS index: {str(e)}")
|
| 54 |
+
raise
|
| 55 |
|
| 56 |
# Otherwise, create new index
|
| 57 |
+
logger_knowledge.info(f"Creating new FAISS index from {self.pdf_path}")
|
| 58 |
|
| 59 |
# Remove old index if recreating
|
| 60 |
if recreate and os.path.exists(self.index_path):
|
| 61 |
import shutil
|
| 62 |
try:
|
| 63 |
shutil.rmtree(self.index_path)
|
| 64 |
+
logger_knowledge.info("Removed old index directory")
|
| 65 |
except Exception as e:
|
| 66 |
+
logger_knowledge.warning(f"Could not remove old index: {e}")
|
| 67 |
|
| 68 |
# Load PDF document
|
| 69 |
if not os.path.exists(self.pdf_path):
|
| 70 |
+
error_msg = f"PDF file not found: {self.pdf_path}"
|
| 71 |
+
logger_knowledge.error(error_msg)
|
| 72 |
+
raise FileNotFoundError(error_msg)
|
| 73 |
|
| 74 |
+
logger_knowledge.info(f"Loading PDF from {self.pdf_path}")
|
| 75 |
+
try:
|
| 76 |
+
loader = PyPDFLoader(self.pdf_path)
|
| 77 |
+
documents = loader.load()
|
| 78 |
+
logger_knowledge.info(f"Loaded {len(documents)} pages from PDF")
|
| 79 |
+
except Exception as e:
|
| 80 |
+
logger_knowledge.error(f"Failed to load PDF: {str(e)}")
|
| 81 |
+
raise
|
| 82 |
|
| 83 |
# Split documents into chunks using RecursiveCharacterTextSplitter
|
| 84 |
+
logger_knowledge.info("Splitting documents into chunks")
|
| 85 |
text_splitter = RecursiveCharacterTextSplitter(
|
| 86 |
chunk_size=800, # Optimized for better granularity
|
| 87 |
chunk_overlap=150, # Reduced proportionally
|
|
|
|
| 89 |
separators=["\n\n", "\n", ". ", ", ", " ", ""] # Paragraph > Line > Sentence > Clause > Word
|
| 90 |
)
|
| 91 |
chunks = text_splitter.split_documents(documents)
|
| 92 |
+
logger_knowledge.info(f"Split into {len(chunks)} chunks")
|
| 93 |
|
| 94 |
# Create FAISS index from chunks
|
| 95 |
+
logger_knowledge.info("Creating FAISS vector store from chunks")
|
| 96 |
+
try:
|
| 97 |
+
vectorstore = FAISS.from_documents(chunks, self.embeddings)
|
| 98 |
+
logger_knowledge.info("FAISS vector store created successfully")
|
| 99 |
+
except Exception as e:
|
| 100 |
+
logger_knowledge.error(f"Failed to create FAISS vector store: {str(e)}")
|
| 101 |
+
raise
|
| 102 |
|
| 103 |
# Save the index
|
| 104 |
+
try:
|
| 105 |
+
vectorstore.save_local(self.index_path)
|
| 106 |
+
logger_knowledge.info(f"Saved FAISS index to {self.index_path}")
|
| 107 |
+
except Exception as e:
|
| 108 |
+
logger_knowledge.error(f"Failed to save FAISS index: {str(e)}")
|
| 109 |
+
raise
|
| 110 |
|
| 111 |
return vectorstore
|
| 112 |
|
|
|
|
| 122 |
List of relevant document chunks
|
| 123 |
"""
|
| 124 |
if not self.vectorstore:
|
| 125 |
+
logger_knowledge.error("Vector store not initialized!")
|
| 126 |
return []
|
| 127 |
|
| 128 |
k = k or self.top_k
|
| 129 |
+
logger_knowledge.debug(f"Retrieving top {k} documents for query")
|
| 130 |
+
|
| 131 |
+
try:
|
| 132 |
+
results = self.vectorstore.similarity_search(query, k=k)
|
| 133 |
+
logger_knowledge.info(f"Retrieved {len(results)} documents")
|
| 134 |
+
return results
|
| 135 |
+
except Exception as e:
|
| 136 |
+
logger_knowledge.error(f"Document retrieval failed: {str(e)}")
|
| 137 |
+
raise
|
| 138 |
|
| 139 |
def retrieve_relevant(self, query: str, k: int = None) -> str:
|
| 140 |
"""
|
|
|
|
| 147 |
Returns:
|
| 148 |
Concatenated text from relevant documents with metadata
|
| 149 |
"""
|
| 150 |
+
logger_knowledge.info(f"Retrieving context for query: {query[:50]}..." if len(query) > 50 else f"Retrieving context for query: {query}")
|
| 151 |
+
|
| 152 |
docs = self.retrieve_relevant_docs(query, k)
|
| 153 |
|
| 154 |
+
if not docs:
|
| 155 |
+
logger_knowledge.warning("No documents retrieved for query")
|
| 156 |
+
return ""
|
| 157 |
+
|
| 158 |
formatted_chunks = []
|
| 159 |
for i, doc in enumerate(docs, 1):
|
| 160 |
chunk_text = f"--- Chunk {i} ---"
|
|
|
|
| 163 |
if doc.metadata:
|
| 164 |
metadata_str = ", ".join([f"{k}: {v}" for k, v in doc.metadata.items()])
|
| 165 |
chunk_text += f"\nMetadata: {metadata_str}"
|
| 166 |
+
logger_knowledge.debug(f"Chunk {i} metadata: {doc.metadata}")
|
| 167 |
|
| 168 |
# Add content
|
| 169 |
+
content_preview = doc.page_content[:100] + "..." if len(doc.page_content) > 100 else doc.page_content
|
| 170 |
+
logger_knowledge.debug(f"Chunk {i} content preview: {content_preview}")
|
| 171 |
chunk_text += f"\n\n{doc.page_content}"
|
| 172 |
formatted_chunks.append(chunk_text)
|
| 173 |
|
| 174 |
+
total_length = sum(len(chunk) for chunk in formatted_chunks)
|
| 175 |
+
logger_knowledge.info(f"Formatted {len(formatted_chunks)} chunks, total length: {total_length} characters")
|
| 176 |
+
|
| 177 |
return "\n\n".join(formatted_chunks)
|
{app → src/app}/memory.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
from typing import List
|
| 4 |
from langchain.messages import HumanMessage, AIMessage
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
class ConversationMemory:
|
|
@@ -10,33 +11,51 @@ class ConversationMemory:
|
|
| 10 |
def __init__(self, max_messages: int = 4):
|
| 11 |
self.messages = []
|
| 12 |
self.max_messages = max_messages
|
|
|
|
| 13 |
|
| 14 |
def add_user_message(self, content: str):
|
| 15 |
"""Add user message to history"""
|
|
|
|
| 16 |
self.messages.append(HumanMessage(content=content))
|
|
|
|
|
|
|
| 17 |
self._truncate()
|
| 18 |
|
| 19 |
def add_ai_message(self, content: str):
|
| 20 |
"""Add AI message to history"""
|
|
|
|
|
|
|
| 21 |
self.messages.append(AIMessage(content=content))
|
|
|
|
|
|
|
| 22 |
self._truncate()
|
| 23 |
|
| 24 |
def _truncate(self):
|
| 25 |
"""Keep only recent messages to avoid context bloat"""
|
| 26 |
if len(self.messages) > self.max_messages:
|
|
|
|
| 27 |
self.messages = self.messages[-self.max_messages:]
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
def get_history(self) -> List:
|
| 30 |
"""Get formatted conversation history"""
|
| 31 |
-
|
|
|
|
|
|
|
| 32 |
|
| 33 |
def get_history_text(self) -> str:
|
| 34 |
"""Get history as formatted text"""
|
| 35 |
if not self.messages:
|
|
|
|
| 36 |
return "No previous conversation"
|
| 37 |
|
| 38 |
history_text = []
|
| 39 |
for msg in self.messages:
|
| 40 |
role = "User" if isinstance(msg, HumanMessage) else "Assistant"
|
| 41 |
history_text.append(f"{role}: {msg.content}")
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from typing import List
|
| 4 |
from langchain.messages import HumanMessage, AIMessage
|
| 5 |
+
from config import logger_memory
|
| 6 |
|
| 7 |
|
| 8 |
class ConversationMemory:
|
|
|
|
| 11 |
def __init__(self, max_messages: int = 4):
|
| 12 |
self.messages = []
|
| 13 |
self.max_messages = max_messages
|
| 14 |
+
logger_memory.info(f"ConversationMemory initialized with max_messages={max_messages}")
|
| 15 |
|
| 16 |
def add_user_message(self, content: str):
|
| 17 |
"""Add user message to history"""
|
| 18 |
+
logger_memory.debug(f"Adding user message: {content[:50]}..." if len(content) > 50 else f"Adding user message: {content}")
|
| 19 |
self.messages.append(HumanMessage(content=content))
|
| 20 |
+
current_count = len(self.messages)
|
| 21 |
+
logger_memory.info(f"User message added. Current message count: {current_count}")
|
| 22 |
self._truncate()
|
| 23 |
|
| 24 |
def add_ai_message(self, content: str):
|
| 25 |
"""Add AI message to history"""
|
| 26 |
+
content_preview = content[:50] + "..." if len(content) > 50 else content
|
| 27 |
+
logger_memory.debug(f"Adding AI message: {content_preview}")
|
| 28 |
self.messages.append(AIMessage(content=content))
|
| 29 |
+
current_count = len(self.messages)
|
| 30 |
+
logger_memory.info(f"AI message added. Current message count: {current_count}")
|
| 31 |
self._truncate()
|
| 32 |
|
| 33 |
def _truncate(self):
|
| 34 |
"""Keep only recent messages to avoid context bloat"""
|
| 35 |
if len(self.messages) > self.max_messages:
|
| 36 |
+
removed_count = len(self.messages) - self.max_messages
|
| 37 |
self.messages = self.messages[-self.max_messages:]
|
| 38 |
+
logger_memory.info(f"Memory truncated: removed {removed_count} oldest messages, keeping {len(self.messages)} most recent")
|
| 39 |
+
else:
|
| 40 |
+
logger_memory.debug(f"No truncation needed. Current count: {len(self.messages)}, max: {self.max_messages}")
|
| 41 |
|
| 42 |
def get_history(self) -> List:
|
| 43 |
"""Get formatted conversation history"""
|
| 44 |
+
history = self.messages
|
| 45 |
+
logger_memory.debug(f"Retrieving history: {len(history)} messages")
|
| 46 |
+
return history
|
| 47 |
|
| 48 |
def get_history_text(self) -> str:
|
| 49 |
"""Get history as formatted text"""
|
| 50 |
if not self.messages:
|
| 51 |
+
logger_memory.debug("No conversation history available")
|
| 52 |
return "No previous conversation"
|
| 53 |
|
| 54 |
history_text = []
|
| 55 |
for msg in self.messages:
|
| 56 |
role = "User" if isinstance(msg, HumanMessage) else "Assistant"
|
| 57 |
history_text.append(f"{role}: {msg.content}")
|
| 58 |
+
|
| 59 |
+
formatted = "\n".join(history_text)
|
| 60 |
+
logger_memory.debug(f"Formatted history: {len(formatted)} characters, {len(self.messages)} messages")
|
| 61 |
+
return formatted
|
{app → src/app}/tools.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
from datetime import datetime
|
| 4 |
from langchain.tools import tool
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
@tool
|
|
@@ -35,38 +36,54 @@ def calculate_metric(metric_name: str, values: str) -> str:
|
|
| 35 |
Returns:
|
| 36 |
A human-readable string containing the official computed metric.
|
| 37 |
"""
|
|
|
|
|
|
|
| 38 |
try:
|
| 39 |
nums = [float(x.strip()) for x in values.split(",")]
|
|
|
|
| 40 |
|
| 41 |
metric = metric_name.lower()
|
|
|
|
| 42 |
|
| 43 |
if metric == "nrr":
|
| 44 |
# Net Revenue Retention = retained revenue / starting revenue
|
| 45 |
if len(nums) >= 2 and nums[1] != 0:
|
| 46 |
result = (nums[0] / nums[1]) * 100
|
| 47 |
-
|
|
|
|
|
|
|
| 48 |
|
| 49 |
elif metric == "stam":
|
| 50 |
# Successful Transactions per Active Merchant
|
| 51 |
if len(nums) >= 2 and nums[1] != 0:
|
| 52 |
result = nums[0] / nums[1]
|
| 53 |
-
|
|
|
|
|
|
|
| 54 |
|
| 55 |
elif metric == "payment_success_rate":
|
| 56 |
# Adjusted Payment Success Rate
|
| 57 |
if len(nums) >= 2 and nums[1] != 0:
|
| 58 |
result = (nums[0] / nums[1]) * 100
|
| 59 |
-
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
|
| 62 |
f"Metric '{metric_name}' could not be computed. "
|
| 63 |
"Please verify the metric name and input values."
|
| 64 |
)
|
|
|
|
|
|
|
| 65 |
|
| 66 |
except Exception as e:
|
| 67 |
-
|
|
|
|
|
|
|
| 68 |
|
| 69 |
@tool
|
| 70 |
def get_current_time() -> str:
|
| 71 |
"""Get the current date and time"""
|
| 72 |
-
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from datetime import datetime
|
| 4 |
from langchain.tools import tool
|
| 5 |
+
from config import logger_app
|
| 6 |
|
| 7 |
|
| 8 |
@tool
|
|
|
|
| 36 |
Returns:
|
| 37 |
A human-readable string containing the official computed metric.
|
| 38 |
"""
|
| 39 |
+
logger_app.info(f"Tool 'calculate_metric' invoked: metric={metric_name}, values={values}")
|
| 40 |
+
|
| 41 |
try:
|
| 42 |
nums = [float(x.strip()) for x in values.split(",")]
|
| 43 |
+
logger_app.debug(f"Parsed values: {nums}")
|
| 44 |
|
| 45 |
metric = metric_name.lower()
|
| 46 |
+
result = None
|
| 47 |
|
| 48 |
if metric == "nrr":
|
| 49 |
# Net Revenue Retention = retained revenue / starting revenue
|
| 50 |
if len(nums) >= 2 and nums[1] != 0:
|
| 51 |
result = (nums[0] / nums[1]) * 100
|
| 52 |
+
output = f"Net Revenue Retention (NRR): {result:.2f}%"
|
| 53 |
+
logger_app.info(f"Calculated NRR: {result:.2f}%")
|
| 54 |
+
return output
|
| 55 |
|
| 56 |
elif metric == "stam":
|
| 57 |
# Successful Transactions per Active Merchant
|
| 58 |
if len(nums) >= 2 and nums[1] != 0:
|
| 59 |
result = nums[0] / nums[1]
|
| 60 |
+
output = f"STAM: {result:.2f} successful transactions per merchant"
|
| 61 |
+
logger_app.info(f"Calculated STAM: {result:.2f}")
|
| 62 |
+
return output
|
| 63 |
|
| 64 |
elif metric == "payment_success_rate":
|
| 65 |
# Adjusted Payment Success Rate
|
| 66 |
if len(nums) >= 2 and nums[1] != 0:
|
| 67 |
result = (nums[0] / nums[1]) * 100
|
| 68 |
+
output = f"Payment Success Rate (Adjusted): {result:.2f}%"
|
| 69 |
+
logger_app.info(f"Calculated Payment Success Rate: {result:.2f}%")
|
| 70 |
+
return output
|
| 71 |
|
| 72 |
+
warning_msg = (
|
| 73 |
f"Metric '{metric_name}' could not be computed. "
|
| 74 |
"Please verify the metric name and input values."
|
| 75 |
)
|
| 76 |
+
logger_app.warning(f"Failed to compute metric '{metric_name}' with values {nums}")
|
| 77 |
+
return warning_msg
|
| 78 |
|
| 79 |
except Exception as e:
|
| 80 |
+
error_msg = f"Error computing metric '{metric_name}': {str(e)}"
|
| 81 |
+
logger_app.error(error_msg)
|
| 82 |
+
return error_msg
|
| 83 |
|
| 84 |
@tool
|
| 85 |
def get_current_time() -> str:
|
| 86 |
"""Get the current date and time"""
|
| 87 |
+
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 88 |
+
logger_app.debug(f"Tool 'get_current_time' invoked: {current_time}")
|
| 89 |
+
return current_time
|
{app → src/app}/ui.py
RENAMED
|
@@ -4,7 +4,8 @@ import gradio as gr
|
|
| 4 |
from typing import Tuple, List
|
| 5 |
|
| 6 |
from .agent import ContextEngineeringAgent
|
| 7 |
-
from config import Settings
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class ContextVisualizerUI:
|
|
@@ -13,13 +14,17 @@ class ContextVisualizerUI:
|
|
| 13 |
def __init__(self):
|
| 14 |
self.agent = None
|
| 15 |
self.chat_history = []
|
|
|
|
| 16 |
|
| 17 |
def initialize_agent(self) -> str:
|
| 18 |
"""Initialize the agent"""
|
|
|
|
| 19 |
try:
|
| 20 |
self.agent = ContextEngineeringAgent()
|
|
|
|
| 21 |
return "Agent initialized successfully"
|
| 22 |
except Exception as e:
|
|
|
|
| 23 |
return f"Error initializing agent: {str(e)}"
|
| 24 |
|
| 25 |
def format_context_layers(self, visualizer) -> str:
|
|
@@ -28,6 +33,7 @@ class ContextVisualizerUI:
|
|
| 28 |
return "<div style='text-align: center; padding: 20px;'>No context layers available</div>"
|
| 29 |
|
| 30 |
total_tokens = sum(visualizer.token_counts.values())
|
|
|
|
| 31 |
|
| 32 |
# Color palette for different layers
|
| 33 |
colors = [
|
|
@@ -118,45 +124,60 @@ class ContextVisualizerUI:
|
|
| 118 |
) -> Tuple[List, str, str, str]:
|
| 119 |
"""Process user query and return results"""
|
| 120 |
|
|
|
|
|
|
|
| 121 |
if not self.agent:
|
|
|
|
| 122 |
self.initialize_agent()
|
| 123 |
|
| 124 |
if not query.strip():
|
|
|
|
| 125 |
return history, "", "", ""
|
| 126 |
|
| 127 |
try:
|
|
|
|
| 128 |
# Process query
|
| 129 |
response, visualizer = self.agent.process_query(query)
|
| 130 |
|
| 131 |
# Add to chat history (format: list of dicts with role and content)
|
| 132 |
history.append({"role": "user", "content": query})
|
| 133 |
history.append({"role": "assistant", "content": response})
|
|
|
|
| 134 |
|
| 135 |
# Format outputs
|
| 136 |
if show_visualization:
|
|
|
|
| 137 |
context_viz_html = self.format_context_layers(visualizer)
|
| 138 |
context_details = self.format_context_details(visualizer)
|
| 139 |
else:
|
|
|
|
| 140 |
context_viz_html = "<div style='text-align: center; padding: 20px; color: #7f8c8d;'>Visualization disabled</div>"
|
| 141 |
context_details = "Visualization disabled"
|
| 142 |
|
|
|
|
| 143 |
return history, "", context_viz_html, context_details
|
| 144 |
|
| 145 |
except Exception as e:
|
| 146 |
error_msg = f"Error processing query: {str(e)}"
|
|
|
|
| 147 |
history.append({"role": "user", "content": query})
|
| 148 |
history.append({"role": "assistant", "content": error_msg})
|
| 149 |
return history, "", "", ""
|
| 150 |
|
| 151 |
def clear_conversation(self) -> Tuple[List, str, str]:
|
| 152 |
"""Clear conversation history"""
|
|
|
|
| 153 |
if self.agent:
|
|
|
|
| 154 |
self.agent.memory.messages = []
|
|
|
|
| 155 |
return [], "", ""
|
| 156 |
|
| 157 |
def create_interface(self) -> gr.Blocks:
|
| 158 |
"""Create the Gradio interface"""
|
| 159 |
|
|
|
|
|
|
|
| 160 |
with gr.Blocks(
|
| 161 |
title="Context Engineering Visualizer"
|
| 162 |
) as interface:
|
|
@@ -285,6 +306,7 @@ class ContextVisualizerUI:
|
|
| 285 |
**Note**: This visualizer uses OpenAI's GPT model and requires an API key in your environment.
|
| 286 |
""")
|
| 287 |
|
|
|
|
| 288 |
return interface
|
| 289 |
|
| 290 |
|
|
@@ -294,6 +316,7 @@ def launch_ui(
|
|
| 294 |
server_port: int = Settings.GRADIO_SERVER_PORT
|
| 295 |
):
|
| 296 |
"""Launch the Gradio interface"""
|
|
|
|
| 297 |
ui = ContextVisualizerUI()
|
| 298 |
interface = ui.create_interface()
|
| 299 |
interface.launch(
|
|
@@ -302,3 +325,4 @@ def launch_ui(
|
|
| 302 |
server_port=server_port,
|
| 303 |
theme=gr.themes.Soft()
|
| 304 |
)
|
|
|
|
|
|
| 4 |
from typing import Tuple, List
|
| 5 |
|
| 6 |
from .agent import ContextEngineeringAgent
|
| 7 |
+
from config.settings import Settings
|
| 8 |
+
from config import logger_ui, logger_app
|
| 9 |
|
| 10 |
|
| 11 |
class ContextVisualizerUI:
|
|
|
|
| 14 |
def __init__(self):
|
| 15 |
self.agent = None
|
| 16 |
self.chat_history = []
|
| 17 |
+
logger_ui.info("ContextVisualizerUI initialized")
|
| 18 |
|
| 19 |
def initialize_agent(self) -> str:
|
| 20 |
"""Initialize the agent"""
|
| 21 |
+
logger_ui.info("Initializing agent from UI")
|
| 22 |
try:
|
| 23 |
self.agent = ContextEngineeringAgent()
|
| 24 |
+
logger_ui.info("Agent initialized successfully from UI")
|
| 25 |
return "Agent initialized successfully"
|
| 26 |
except Exception as e:
|
| 27 |
+
logger_ui.error(f"Error initializing agent: {str(e)}")
|
| 28 |
return f"Error initializing agent: {str(e)}"
|
| 29 |
|
| 30 |
def format_context_layers(self, visualizer) -> str:
|
|
|
|
| 33 |
return "<div style='text-align: center; padding: 20px;'>No context layers available</div>"
|
| 34 |
|
| 35 |
total_tokens = sum(visualizer.token_counts.values())
|
| 36 |
+
logger_ui.debug(f"Formatting context layers: {len(visualizer.context_layers)} layers, {total_tokens} total tokens")
|
| 37 |
|
| 38 |
# Color palette for different layers
|
| 39 |
colors = [
|
|
|
|
| 124 |
) -> Tuple[List, str, str, str]:
|
| 125 |
"""Process user query and return results"""
|
| 126 |
|
| 127 |
+
logger_ui.info(f"Processing query from UI: {query[:50]}..." if len(query) > 50 else f"Processing query from UI: {query}")
|
| 128 |
+
|
| 129 |
if not self.agent:
|
| 130 |
+
logger_ui.info("Agent not initialized, initializing now")
|
| 131 |
self.initialize_agent()
|
| 132 |
|
| 133 |
if not query.strip():
|
| 134 |
+
logger_ui.warning("Empty query received, skipping")
|
| 135 |
return history, "", "", ""
|
| 136 |
|
| 137 |
try:
|
| 138 |
+
logger_ui.info("Delegating query processing to agent")
|
| 139 |
# Process query
|
| 140 |
response, visualizer = self.agent.process_query(query)
|
| 141 |
|
| 142 |
# Add to chat history (format: list of dicts with role and content)
|
| 143 |
history.append({"role": "user", "content": query})
|
| 144 |
history.append({"role": "assistant", "content": response})
|
| 145 |
+
logger_ui.info(f"Added exchange to chat history. Total messages: {len(history)}")
|
| 146 |
|
| 147 |
# Format outputs
|
| 148 |
if show_visualization:
|
| 149 |
+
logger_ui.debug("Formatting context visualization")
|
| 150 |
context_viz_html = self.format_context_layers(visualizer)
|
| 151 |
context_details = self.format_context_details(visualizer)
|
| 152 |
else:
|
| 153 |
+
logger_ui.debug("Visualization disabled by user")
|
| 154 |
context_viz_html = "<div style='text-align: center; padding: 20px; color: #7f8c8d;'>Visualization disabled</div>"
|
| 155 |
context_details = "Visualization disabled"
|
| 156 |
|
| 157 |
+
logger_ui.info("Query processed successfully")
|
| 158 |
return history, "", context_viz_html, context_details
|
| 159 |
|
| 160 |
except Exception as e:
|
| 161 |
error_msg = f"Error processing query: {str(e)}"
|
| 162 |
+
logger_ui.error(error_msg)
|
| 163 |
history.append({"role": "user", "content": query})
|
| 164 |
history.append({"role": "assistant", "content": error_msg})
|
| 165 |
return history, "", "", ""
|
| 166 |
|
| 167 |
def clear_conversation(self) -> Tuple[List, str, str]:
|
| 168 |
"""Clear conversation history"""
|
| 169 |
+
logger_ui.info("Clearing conversation history")
|
| 170 |
if self.agent:
|
| 171 |
+
previous_count = len(self.agent.memory.messages)
|
| 172 |
self.agent.memory.messages = []
|
| 173 |
+
logger_ui.info(f"Cleared {previous_count} messages from conversation memory")
|
| 174 |
return [], "", ""
|
| 175 |
|
| 176 |
def create_interface(self) -> gr.Blocks:
|
| 177 |
"""Create the Gradio interface"""
|
| 178 |
|
| 179 |
+
logger_ui.info("Creating Gradio interface")
|
| 180 |
+
|
| 181 |
with gr.Blocks(
|
| 182 |
title="Context Engineering Visualizer"
|
| 183 |
) as interface:
|
|
|
|
| 306 |
**Note**: This visualizer uses OpenAI's GPT model and requires an API key in your environment.
|
| 307 |
""")
|
| 308 |
|
| 309 |
+
logger_ui.info("Gradio interface created successfully")
|
| 310 |
return interface
|
| 311 |
|
| 312 |
|
|
|
|
| 316 |
server_port: int = Settings.GRADIO_SERVER_PORT
|
| 317 |
):
|
| 318 |
"""Launch the Gradio interface"""
|
| 319 |
+
logger_app.info(f"Launching UI with settings: share={share}, server_name={server_name}, server_port={server_port}")
|
| 320 |
ui = ContextVisualizerUI()
|
| 321 |
interface = ui.create_interface()
|
| 322 |
interface.launch(
|
|
|
|
| 325 |
server_port=server_port,
|
| 326 |
theme=gr.themes.Soft()
|
| 327 |
)
|
| 328 |
+
logger_app.info("UI launched successfully")
|
{app → src/app}/visualizer.py
RENAMED
|
File without changes
|
src/config/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Configuration package for the Context Engineering Visualizer"""
|
| 2 |
+
|
| 3 |
+
from .settings import Settings
|
| 4 |
+
from .logging_config import setup_logger, get_logger, logger_agent, logger_ui, logger_knowledge, logger_memory, logger_app
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"Settings",
|
| 8 |
+
"setup_logger",
|
| 9 |
+
"get_logger",
|
| 10 |
+
"logger_agent",
|
| 11 |
+
"logger_ui",
|
| 12 |
+
"logger_knowledge",
|
| 13 |
+
"logger_memory",
|
| 14 |
+
"logger_app",
|
| 15 |
+
]
|
src/config/logging_config.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Centralized logging configuration for the Context Engineering Visualizer."""
|
| 2 |
+
import logging
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
# Get project root (parent of src directory)
|
| 7 |
+
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
| 8 |
+
|
| 9 |
+
# Default log file location - logs/app.log in project root
|
| 10 |
+
DEFAULT_LOG_FILE = PROJECT_ROOT / 'logs' / 'app.log'
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def setup_logger(name: str = "context_visualizer", log_file: str = None, level: int = logging.INFO) -> logging.Logger:
|
| 14 |
+
"""Setup centralized logging for the application.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
name: Logger name (default: "context_visualizer")
|
| 18 |
+
log_file: Optional log file path. If None, only console logging is enabled.
|
| 19 |
+
Defaults to DEFAULT_LOG_FILE if not specified.
|
| 20 |
+
level: Logging level (default: logging.INFO)
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
Configured logger instance
|
| 24 |
+
"""
|
| 25 |
+
logger = logging.getLogger(name)
|
| 26 |
+
logger.setLevel(level)
|
| 27 |
+
|
| 28 |
+
# Remove existing handlers to avoid duplicates
|
| 29 |
+
logger.handlers.clear()
|
| 30 |
+
|
| 31 |
+
# Console handler - prints to terminal
|
| 32 |
+
console_handler = logging.StreamHandler(sys.stdout)
|
| 33 |
+
console_handler.setLevel(level)
|
| 34 |
+
console_formatter = logging.Formatter(
|
| 35 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 36 |
+
datefmt='%Y-%m-%d %H:%M:%S'
|
| 37 |
+
)
|
| 38 |
+
console_handler.setFormatter(console_formatter)
|
| 39 |
+
logger.addHandler(console_handler)
|
| 40 |
+
|
| 41 |
+
# File handler - saves to logs/app.log
|
| 42 |
+
if log_file is not False: # False means explicitly disable file logging
|
| 43 |
+
log_path = Path(log_file) if log_file else DEFAULT_LOG_FILE
|
| 44 |
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
| 45 |
+
|
| 46 |
+
file_handler = logging.FileHandler(log_path, encoding='utf-8')
|
| 47 |
+
file_handler.setLevel(logging.DEBUG) # More detailed logging in file
|
| 48 |
+
file_formatter = logging.Formatter(
|
| 49 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
|
| 50 |
+
datefmt='%Y-%m-%d %H:%M:%S'
|
| 51 |
+
)
|
| 52 |
+
file_handler.setFormatter(file_formatter)
|
| 53 |
+
logger.addHandler(file_handler)
|
| 54 |
+
|
| 55 |
+
return logger
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_logger(name: str = None) -> logging.Logger:
|
| 59 |
+
"""Get an existing logger or create a new one with default settings.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
name: Logger name. If None, returns the root 'context_visualizer' logger.
|
| 63 |
+
|
| 64 |
+
Returns:
|
| 65 |
+
Logger instance
|
| 66 |
+
"""
|
| 67 |
+
if name:
|
| 68 |
+
return logging.getLogger(f"context_visualizer.{name}")
|
| 69 |
+
return logging.getLogger("context_visualizer")
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Pre-configured loggers for key components
|
| 73 |
+
logger_agent = get_logger("agent")
|
| 74 |
+
logger_ui = get_logger("ui")
|
| 75 |
+
logger_knowledge = get_logger("knowledge")
|
| 76 |
+
logger_memory = get_logger("memory")
|
| 77 |
+
logger_app = get_logger("app")
|
{config → src/config}/settings.py
RENAMED
|
@@ -22,8 +22,8 @@ class Settings:
|
|
| 22 |
RAG_TOP_K = 3
|
| 23 |
|
| 24 |
# Vector Store Configuration
|
| 25 |
-
FAISS_INDEX_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "faiss_index_store")
|
| 26 |
-
PDF_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "Product Strategy & Decision Handbook — Atlas Pay.pdf")
|
| 27 |
|
| 28 |
# UI Settings
|
| 29 |
GRADIO_SHARE = False
|
|
|
|
| 22 |
RAG_TOP_K = 3
|
| 23 |
|
| 24 |
# Vector Store Configuration
|
| 25 |
+
FAISS_INDEX_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "faiss_index_store")
|
| 26 |
+
PDF_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "data", "Product Strategy & Decision Handbook — Atlas Pay.pdf")
|
| 27 |
|
| 28 |
# UI Settings
|
| 29 |
GRADIO_SHARE = False
|
src/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Utility scripts for Context Engineering Visualizer"""
|
{app → src/utils}/process_knowledge.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
import sys
|
| 4 |
import os
|
| 5 |
|
| 6 |
-
# Add parent directory to path
|
| 7 |
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
| 8 |
|
| 9 |
from config.settings import Settings
|
|
|
|
| 3 |
import sys
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
# Add parent directory to path (src/)
|
| 7 |
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
| 8 |
|
| 9 |
from config.settings import Settings
|
uv.lock
CHANGED
|
@@ -453,12 +453,12 @@ dependencies = [
|
|
| 453 |
[package.metadata]
|
| 454 |
requires-dist = [
|
| 455 |
{ name = "faiss-cpu", specifier = ">=1.13.2" },
|
| 456 |
-
{ name = "gradio", specifier = ">=6.
|
| 457 |
{ name = "ipykernel", specifier = ">=7.1.0" },
|
| 458 |
-
{ name = "langchain", specifier = ">=1.2.
|
| 459 |
{ name = "langchain-community", specifier = ">=0.4.1" },
|
| 460 |
{ name = "langchain-openai", specifier = ">=1.1.7" },
|
| 461 |
-
{ name = "pypdf", specifier = ">=6.6.
|
| 462 |
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
| 463 |
{ name = "tiktoken", specifier = ">=0.12.0" },
|
| 464 |
]
|
|
|
|
| 453 |
[package.metadata]
|
| 454 |
requires-dist = [
|
| 455 |
{ name = "faiss-cpu", specifier = ">=1.13.2" },
|
| 456 |
+
{ name = "gradio", specifier = ">=6.5.1" },
|
| 457 |
{ name = "ipykernel", specifier = ">=7.1.0" },
|
| 458 |
+
{ name = "langchain", specifier = ">=1.2.8" },
|
| 459 |
{ name = "langchain-community", specifier = ">=0.4.1" },
|
| 460 |
{ name = "langchain-openai", specifier = ">=1.1.7" },
|
| 461 |
+
{ name = "pypdf", specifier = ">=6.6.2" },
|
| 462 |
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
| 463 |
{ name = "tiktoken", specifier = ">=0.12.0" },
|
| 464 |
]
|