Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,27 @@ scripts/deploy_to_vm.ps1
venv/

# User-specific local workspace configuration
library/config/.esim/workspace.txt
**/library/config/.esim/workspace.txt

# Local configuration files generated at runtime
library/config/.esim/config.ini
library/config/.nghdl/
**/library/config/.esim/config.ini
**/library/config/.nghdl/

# Ensure compiled files and caches under nghdl/ are also ignored
**/__pycache__/
**/*.pyc

# Local AI test scripts
test_ai_features.py
test_exact_log.py
test_regex2.py
test_error_pipeline_regression.py
test_netlist_pipeline_regression.py
test_ollama_error.py
test_ollama_netlist.py



# Local test projects
Examples/ErrorTests/

2 changes: 1 addition & 1 deletion README_CHATBOT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# eSim Copilot – AI-Assisted Electronics Simulation Tool
# eSim AI Assistant – AI-Assisted Electronics Simulation Tool

eSim Copilot is an AI-powered assistant integrated into **eSim**, designed to help users analyze electronic circuits, debug SPICE netlists, understand simulation errors, and interact using text, voice, and images.

Expand Down
42 changes: 24 additions & 18 deletions src/chatbot/chatbot_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import re
import json
from typing import Dict, Any, Tuple, List
from .error_solutions import get_error_solution
from .error_patterns import match_error_patterns
from .error_solutions import get_solution_for_category
from .image_handler import analyze_and_extract
from .ollama_runner import run_ollama
from .knowledge_base import search_knowledge
Expand Down Expand Up @@ -125,7 +126,7 @@ def answer_with_rag_fallback(user_input: str) -> str:

if rag_context.strip():
prompt = f"""
You are eSim Copilot.
You are eSim AI Assistant.

Use ONLY the following official eSim documentation
to answer the question. Do NOT invent information.
Expand Down Expand Up @@ -344,12 +345,12 @@ def is_semantic_topic_switch(
np.linalg.norm(emb_new) * np.linalg.norm(emb_prev)
)

print(f"[COPILOT] Semantic similarity = {similarity:.3f}")
print(f"[AI ASSISTANT] Semantic similarity = {similarity:.3f}")

return similarity < threshold

except Exception as e:
print(f"[COPILOT] Topic switch check failed: {e}")
print(f"[AI ASSISTANT] Topic switch check failed: {e}")
return False

# ==================== QUESTION CLASSIFICATION ====================
Expand Down Expand Up @@ -385,7 +386,7 @@ def classify_question_type(user_input: str, has_image_context: bool,

is_followup = _is_follow_up_question(user_input, history)
if is_semantic_topic_switch(user_input, history):
print("[COPILOT] Topic switch detected (semantic)")
print("[AI ASSISTANT] Topic switch detected (semantic)")
is_followup = False

if not is_followup:
Expand Down Expand Up @@ -414,7 +415,7 @@ def classify_question_type(user_input: str, has_image_context: bool,

def handle_greeting() -> str:
return (
"Hello! I'm eSim Copilot. I can help you with:\n"
"Hello! I'm eSim AI Assistant. I can help you with:\n"
"• Circuit analysis and netlist debugging\n"
"• Electronics concepts and SPICE simulation\n"
"• Component selection and circuit design\n\n"
Expand All @@ -426,7 +427,7 @@ def handle_simple_question(user_input: str) -> str:
"""
Handles standalone questions.
Uses RAG first, then falls back to Ollama.
keep in mind that your a copilot of eSim an EDA tool
keep in mind that you are an AI assistant of eSim an EDA tool
"""
return answer_with_rag_fallback(user_input)

Expand Down Expand Up @@ -489,18 +490,23 @@ def handle_esim_question(user_input: str,
"""
user_lower = user_input.lower()

sol = get_error_solution(user_input)
if sol and sol.get("description") != "General schematic error":
matches = match_error_patterns(user_input)
if matches:
root_match = min(matches, key=lambda m: m.causal_priority)
sol = get_solution_for_category(root_match.category)

fixes = "\n".join(f"- {f}" for f in sol.get("fixes", []))
cmd = sol.get("eSim_command", "")
steps = "\n".join(f"- {s}" for s in sol.get("esim_steps", []))

answer = (
f"**Detected issue:** {sol['description']}\n"
f"**Detected issue:** {sol['category']}\n"
f"**Severity:** {sol.get('severity', 'unknown')}\n\n"
f"**Recommended fixes:**\n{fixes}\n\n"
)
if cmd:
answer += f"**eSim action:** {cmd}\n"
return answer_with_rag_fallback(user_input)
if steps:
answer += f"**eSim Steps:**\n{steps}\n"

return answer

history_text = _history_to_text(history, max_turns=6)

Expand Down Expand Up @@ -646,7 +652,7 @@ def handle_input(user_input: str,
question_type = classify_question_type(
user_input, bool(LAST_IMAGE_CONTEXT), history
)
print(f"[COPILOT] Question type: {question_type}")
print(f"[AI ASSISTANT] Question type: {question_type}")

try:
if question_type == "netlist":
Expand Down Expand Up @@ -674,13 +680,13 @@ def handle_input(user_input: str,

except Exception as e:
error_msg = f"Error processing question: {str(e)}"
print(f"[COPILOT ERROR] {error_msg}")
print(f"[AI ASSISTANT ERROR] {error_msg}")
return error_msg


# ==================== WRAPPER ====================

class ESIMCopilotWrapper:
class ESIMAssistantWrapper:
def __init__(self) -> None:
self.history: List[Dict[str, str]] = []

Expand All @@ -694,7 +700,7 @@ def handle_input(self, user_input: str) -> str:
def analyze_schematic(self, query: str) -> str:
return self.handle_input(query)

_GLOBAL_WRAPPER = ESIMCopilotWrapper()
_GLOBAL_WRAPPER = ESIMAssistantWrapper()


def analyze_schematic(query: str) -> str:
Expand Down
36 changes: 24 additions & 12 deletions src/chatbot/chatbot_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import time
import threading
import ollama
from PyQt6.QtCore import QThread, pyqtSignal
from PyQt5.QtCore import QThread, pyqtSignal
from chatbot.knowledge_base import search_knowledge
# ── Optional imports ──────────────────────────────────────────────────────────

Expand Down Expand Up @@ -293,7 +293,7 @@ def _smart_num_predict(user_messages: list, user_override: int = 1024) -> int:
if is_simple and not is_long:
budget = 128
elif is_complex or is_long:
budget = 512
budget = 1024
else:
budget = 256

Expand All @@ -308,12 +308,14 @@ class OllamaWorker(QThread):
chunk_signal = pyqtSignal(str)

def __init__(self, chat_history, model="",
temperature=0.25, num_predict=1024):
temperature=0.25, num_predict=1024, system_prompt=None, netlist_formatter_context=None):
super().__init__()
self.chat_history = chat_history
self.model = model
self.temperature = temperature
self.num_predict = num_predict
self.system_prompt = system_prompt
self.netlist_formatter_context = netlist_formatter_context
self._stop_requested = False

def stop(self):
Expand All @@ -327,7 +329,8 @@ def run(self):

# config-driven history window + system prompt
max_lines = int(CONFIG.get("history", {}).get("max_lines", 6))
messages = [{"role": "system", "content": _SYSTEM_PROMPT}]
active_system_prompt = self.system_prompt if self.system_prompt else _SYSTEM_PROMPT
messages = [{"role": "system", "content": active_system_prompt}]
for line in self.chat_history[-max_lines:]:
if line.startswith("User:"):
messages.append({"role": "user", "content": line[5:].strip()})
Expand All @@ -341,6 +344,9 @@ def run(self):
repeat_pen = float(CONFIG.get("sampling", {}).get("repeat_penalty", 1.08))
keep_alive = CONFIG.get("runtime", {}).get("keep_alive", "-1m")

if self.netlist_formatter_context:
self.chunk_signal.emit("### Circuit Overview\n")

stream = ollama.chat(
model=self.model,
messages=messages,
Expand All @@ -350,8 +356,8 @@ def run(self):
"num_predict": budget,
"num_ctx": num_ctx,
"repeat_penalty": repeat_pen,
"keep_alive": keep_alive,
}
},
keep_alive=keep_alive
)

bot_response = ""
Expand All @@ -365,18 +371,24 @@ def run(self):

bot_response = bot_response.strip()
if not bot_response:
bot_response = (
"⚠️ Received an empty response. "
"The model may still be loading — please try again."
)
bot_response = "Circuit analysis unavailable. See components and simulation details below." if self.netlist_formatter_context else "⚠️ Received an empty response. Please verify the AI model is downloaded and try again."

if self.netlist_formatter_context and not self._stop_requested:
from src.chatbot.netlist_analysis import format_netlist_table
table_md = format_netlist_table(self.netlist_formatter_context)
bot_response += "\n\n" + table_md

if self.netlist_formatter_context:
bot_response = "### Circuit Overview\n" + bot_response

self.response_signal.emit(bot_response)

except Exception as e:
bot_response = (
f"❌ Error: {str(e)}\n"
"Make sure Ollama is installed and 'ollama serve' is running."
)

self.response_signal.emit(bot_response)
self.response_signal.emit(bot_response)


# ── Vision model helpers ──────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion src/chatbot/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"system_rules": {
"text_system_prompt": "You are the eSim AI assistant. Rules:\n- ALWAYS answer in exactly 3 bullet points.\n- ALWAYS end every reply with this exact line: powered by eSim Copilot",
"text_system_prompt": "You are the eSim AI assistant. Rules:\n- ALWAYS answer in exactly 3 bullet points.\n- ALWAYS end every reply with this exact line: powered by eSim AI Assistant",
"vision_system_prompt": "You are an expert electronics engineer and the AI assistant inside eSim, an open-source EDA tool by FOSSEE at IIT Bombay. You are given schematic images from eSim or KiCad. Read every visible label, net name, component reference, value and pin number, and answer the user's question accurately. Never refuse to analyse. Be concise and use the visible reference designators (R1, C3, U2, etc.)."
},
"context_window": {
Expand Down
Loading