mcikalmerdeka commited on
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 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
- result = self.agent.invoke({
107
- "messages": [{"role": "user", "content": context_message}]
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
- print(f"Loading existing FAISS index from {self.index_path}")
37
- return FAISS.load_local(
38
- self.index_path,
39
- self.embeddings,
40
- allow_dangerous_deserialization=True
41
- )
 
 
 
 
 
 
42
 
43
  # Otherwise, create new index
44
- print(f"Creating new FAISS index from {self.pdf_path}")
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
- print("Removed old index directory")
52
  except Exception as e:
53
- print(f"Warning: Could not remove old index: {e}")
54
 
55
  # Load PDF document
56
  if not os.path.exists(self.pdf_path):
57
- raise FileNotFoundError(f"PDF file not found: {self.pdf_path}")
 
 
58
 
59
- loader = PyPDFLoader(self.pdf_path)
60
- documents = loader.load()
61
- print(f"Loaded {len(documents)} pages from PDF")
 
 
 
 
 
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
- print(f"Split into {len(chunks)} chunks")
72
 
73
  # Create FAISS index from chunks
74
- vectorstore = FAISS.from_documents(chunks, self.embeddings)
 
 
 
 
 
 
75
 
76
  # Save the index
77
- vectorstore.save_local(self.index_path)
78
- print(f"Saved FAISS index to {self.index_path}")
 
 
 
 
79
 
80
  return vectorstore
81
 
@@ -91,11 +122,19 @@ class KnowledgeBase:
91
  List of relevant document chunks
92
  """
93
  if not self.vectorstore:
94
- print("Warning: Vector store not initialized!")
95
  return []
96
 
97
  k = k or self.top_k
98
- return self.vectorstore.similarity_search(query, k=k)
 
 
 
 
 
 
 
 
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
- return self.messages
 
 
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
- return "\n".join(history_text)
 
 
 
 
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
- return f"Net Revenue Retention (NRR): {result:.2f}%"
 
 
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
- return f"STAM: {result:.2f} successful transactions per merchant"
 
 
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
- return f"Payment Success Rate (Adjusted): {result:.2f}%"
 
 
60
 
61
- return (
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
- return f"Error computing metric '{metric_name}': {str(e)}"
 
 
68
 
69
  @tool
70
  def get_current_time() -> str:
71
  """Get the current date and time"""
72
- return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
 
 
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.3.0" },
457
  { name = "ipykernel", specifier = ">=7.1.0" },
458
- { name = "langchain", specifier = ">=1.2.6" },
459
  { name = "langchain-community", specifier = ">=0.4.1" },
460
  { name = "langchain-openai", specifier = ">=1.1.7" },
461
- { name = "pypdf", specifier = ">=6.6.0" },
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
  ]