tinymentor 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinymentor
3
+ Version: 0.3.0
4
+ Summary: Notebook-first local GenAI coding copilot
5
+ Author-email: tinymentor <tinymentor@example.com>
6
+ Requires-Python: >=3.8
7
+ Requires-Dist: tinymentor-inference>=0.2.62
8
+ Requires-Dist: scikit-learn
9
+ Requires-Dist: tinymentor-model-part1
10
+ Requires-Dist: tinymentor-model-part2
11
+ Requires-Dist: tinymentor-model-part3
12
+ Requires-Dist: tinymentor-model-part4
13
+ Requires-Dist: tinymentor-model-part5
14
+ Requires-Dist: tinymentor-model-part6
15
+ Requires-Dist: tinymentor-model-part7
16
+ Requires-Dist: tinymentor-model-part8
17
+ Requires-Dist: tinymentor-model-part9
18
+ Requires-Dist: tinymentor-model-part10
19
+ Requires-Dist: tinymentor-model-part11
20
+ Requires-Dist: tinymentor-model-part12
21
+ Requires-Dist: tinymentor-model-part13
22
+ Requires-Dist: tinymentor-model-part14
23
+ Requires-Dist: tinymentor-model-part15
24
+ Requires-Dist: tinymentor-model-part16
25
+ Requires-Dist: tinymentor-model-part17
26
+ Requires-Dist: tinymentor-model-part18
27
+ Requires-Dist: tinymentor-model-part19
28
+ Requires-Dist: tinymentor-model-part20
29
+ Requires-Dist: tinymentor-model-part21
30
+ Requires-Dist: tinymentor-model-part22
@@ -0,0 +1,20 @@
1
+ # tinymentor
2
+
3
+ A notebook-first AI coding assistant focused on:
4
+ - GenAI coding questions
5
+ - Debugging
6
+ - Code generation
7
+ - Retrieval
8
+ - Learning from approved answers
9
+ - Future fine-tuning
10
+
11
+ Fully local, offline, and simple to use.
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ from tinymentor import ask, approve
17
+
18
+ ask("Why ModuleNotFoundError happens?")
19
+ approve()
20
+ ```
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tinymentor"
7
+ version = "0.3.0"
8
+ authors = [{name="tinymentor", email="tinymentor@example.com"}]
9
+ description = "Notebook-first local GenAI coding copilot"
10
+ requires-python = ">=3.8"
11
+ dependencies = [
12
+ "tinymentor-inference>=0.2.62",
13
+ "scikit-learn",
14
+ "tinymentor-model-part1",
15
+ "tinymentor-model-part2",
16
+ "tinymentor-model-part3",
17
+ "tinymentor-model-part4",
18
+ "tinymentor-model-part5",
19
+ "tinymentor-model-part6",
20
+ "tinymentor-model-part7",
21
+ "tinymentor-model-part8",
22
+ "tinymentor-model-part9",
23
+ "tinymentor-model-part10",
24
+ "tinymentor-model-part11",
25
+ "tinymentor-model-part12",
26
+ "tinymentor-model-part13",
27
+ "tinymentor-model-part14",
28
+ "tinymentor-model-part15",
29
+ "tinymentor-model-part16",
30
+ "tinymentor-model-part17",
31
+ "tinymentor-model-part18",
32
+ "tinymentor-model-part19",
33
+ "tinymentor-model-part20",
34
+ "tinymentor-model-part21",
35
+ "tinymentor-model-part22"
36
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .ask import ask
2
+ from .learning import approve
3
+
4
+ __all__ = ["ask", "approve"]
@@ -0,0 +1,48 @@
1
+ from .memory import remember
2
+ from .retrieval import search_notes
3
+ from .errors import solve_error
4
+ from .model import generate_response
5
+
6
+ def ask(question: str, traceback: str = None):
7
+ """
8
+ Main Ask Workflow:
9
+ Question -> Memory -> Retrieve top 5 notes -> Error detection ->
10
+ Qwen model -> Generate response -> Return answer
11
+ """
12
+ print(f"Analyzing: {question}")
13
+
14
+ # 1. Retrieve top 5 notes
15
+ notes = search_notes(question)
16
+ context_str = ""
17
+ if notes:
18
+ context_str = "\n\nRelevant Notes:\n" + "\n---\n".join(notes)
19
+
20
+ # 2. Error detection
21
+ error_context = solve_error(traceback)
22
+ if error_context:
23
+ error_context = f"\n\nContext from traceback:\n{error_context}"
24
+
25
+ # 3. Formulate Prompt
26
+ # Emphasize explanation, debugging, code, dry run
27
+ prompt = f"""You are tinymentor, a local GenAI coding copilot.
28
+ Provide: explanation, debugging, code, and dry run if applicable.
29
+ {context_str}
30
+ {error_context}
31
+
32
+ Q: {question}
33
+ A:"""
34
+
35
+ # 4. Qwen model -> Generate response
36
+ try:
37
+ answer = generate_response(prompt)
38
+ except Exception as e:
39
+ answer = f"Error during generation: {e}"
40
+ print(answer)
41
+ return
42
+
43
+ # 5. Memory
44
+ remember(question, answer)
45
+
46
+ # 6. Return answer
47
+ print("\n" + answer)
48
+ return answer
@@ -0,0 +1,42 @@
1
+ def classify_topic(question: str) -> str:
2
+ """
3
+ Auto-classify topic to decide where to save.
4
+ Returns a string like 'genai/rag' or 'debugging/python'.
5
+ """
6
+ q_lower = question.lower()
7
+
8
+ if "rag" in q_lower:
9
+ return "genai/rag"
10
+ elif "embed" in q_lower:
11
+ return "genai/embeddings"
12
+ elif "prompt" in q_lower:
13
+ return "genai/prompt_engineering"
14
+ elif "vector" in q_lower:
15
+ return "genai/vector_db"
16
+ elif "llm" in q_lower:
17
+ return "genai/llm"
18
+
19
+ elif "module" in q_lower or "import" in q_lower:
20
+ return "debugging/python"
21
+ elif "notebook" in q_lower or "jupyter" in q_lower:
22
+ return "debugging/notebook"
23
+ elif "pip" in q_lower or "install" in q_lower:
24
+ return "debugging/pip"
25
+ elif "api" in q_lower:
26
+ return "debugging/api"
27
+
28
+ elif "javascript" in q_lower or "js" in q_lower:
29
+ return "coding/javascript"
30
+ elif "sql" in q_lower:
31
+ return "coding/sql"
32
+
33
+ return "coding/python"
34
+
35
+ def generate_filename(question: str) -> str:
36
+ """
37
+ Generate a safe, short filename based on the question.
38
+ """
39
+ words = [w for w in question.lower().split() if w.isalnum()]
40
+ if not words:
41
+ return "note.md"
42
+ return "_".join(words[:4]) + ".md"
@@ -0,0 +1,40 @@
1
+ import traceback
2
+ import sys
3
+
4
+ def solve_error(tb_str: str = None) -> str:
5
+ """
6
+ Detect errors such as ModuleNotFoundError, PIP errors, notebook errors, etc.
7
+ """
8
+ if tb_str is None:
9
+ try:
10
+ exc_type, exc_value, exc_traceback = sys.exc_info()
11
+ if exc_type is not None:
12
+ tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
13
+ else:
14
+ return ""
15
+ except Exception:
16
+ return ""
17
+
18
+ detected_errors = []
19
+ error_types = [
20
+ "ModuleNotFoundError", "ImportError", "TypeError",
21
+ "KeyError", "IndexError", "ValueError"
22
+ ]
23
+
24
+ for err in error_types:
25
+ if err in tb_str:
26
+ detected_errors.append(err)
27
+
28
+ if "pip" in tb_str.lower():
29
+ detected_errors.append("pip error")
30
+
31
+ if "ipython" in tb_str.lower() or "jupyter" in tb_str.lower():
32
+ detected_errors.append("notebook error")
33
+
34
+ if "api" in tb_str.lower() or "requests.exceptions" in tb_str:
35
+ detected_errors.append("API error")
36
+
37
+ if detected_errors:
38
+ return f"Detected Errors: {', '.join(detected_errors)}\nTraceback context:\n{tb_str}"
39
+
40
+ return ""
@@ -0,0 +1,70 @@
1
+ import os
2
+ from .memory import get_last_qa, get_full_memory
3
+ from .classifier import classify_topic, generate_filename
4
+
5
+ def approve():
6
+ """
7
+ Learning workflow:
8
+ 1. Get last Q&A
9
+ 2. Classify topic
10
+ 3. Create folder if missing
11
+ 4. Generate markdown filename
12
+ 5. Save markdown
13
+ 6. Merge similar topics / avoid duplicates
14
+ 7. Update approved dataset if >= 50 answers
15
+ """
16
+ last_qa = get_last_qa()
17
+ if not last_qa:
18
+ print("No recent Q&A to approve.")
19
+ return
20
+
21
+ question = last_qa["question"]
22
+ answer = last_qa["answer"]
23
+
24
+ topic_path = classify_topic(question)
25
+ base_dir = os.path.dirname(__file__)
26
+ full_topic_dir = os.path.join(base_dir, "md", topic_path)
27
+
28
+ os.makedirs(full_topic_dir, exist_ok=True)
29
+
30
+ filename = generate_filename(question)
31
+ filepath = os.path.join(full_topic_dir, filename)
32
+
33
+ # Avoid duplicates/Merge
34
+ if os.path.exists(filepath):
35
+ with open(filepath, "r") as f:
36
+ existing_content = f.read()
37
+ if question in existing_content:
38
+ print(f"Already learned: {filepath}")
39
+ else:
40
+ with open(filepath, "a") as f:
41
+ f.write(f"\n\n## {question}\n\n{answer}\n")
42
+ print(f"Merged into existing note: {filepath}")
43
+ else:
44
+ with open(filepath, "w") as f:
45
+ f.write(f"# Notes on {topic_path}\n\n## {question}\n\n{answer}\n")
46
+ print(f"Learned and saved to: {filepath}")
47
+
48
+ # Check for dataset creation
49
+ _check_dataset_threshold(base_dir)
50
+
51
+ def _check_dataset_threshold(base_dir):
52
+ """
53
+ Begin dataset creation after 50 approved Q&A
54
+ """
55
+ # Count approved files or answers
56
+ count = 0
57
+ md_dir = os.path.join(base_dir, "md")
58
+ for root, _, files in os.walk(md_dir):
59
+ for file in files:
60
+ if file.endswith(".md"):
61
+ count += 1
62
+
63
+ if count >= 50:
64
+ training_dir = os.path.join(base_dir, "training")
65
+ os.makedirs(training_dir, exist_ok=True)
66
+ dataset_path = os.path.join(training_dir, "approved_dataset.md")
67
+ if not os.path.exists(dataset_path):
68
+ with open(dataset_path, "w") as f:
69
+ f.write("# TinyMentor Approved Dataset\n\nDataset creation initiated.\n")
70
+ print("Reached 50 approved notes. Started approved_dataset.md for future fine-tuning.")
@@ -0,0 +1,37 @@
1
+ import os
2
+ import json
3
+
4
+ MEMORY_FILE = os.path.join(os.path.dirname(__file__), "memory", "session.json")
5
+
6
+ def _load_memory():
7
+ if os.path.exists(MEMORY_FILE):
8
+ with open(MEMORY_FILE, "r") as f:
9
+ try:
10
+ return json.load(f)
11
+ except json.JSONDecodeError:
12
+ return []
13
+ return []
14
+
15
+ def _save_memory(memory):
16
+ os.makedirs(os.path.dirname(MEMORY_FILE), exist_ok=True)
17
+ with open(MEMORY_FILE, "w") as f:
18
+ json.dump(memory, f, indent=2)
19
+
20
+ def remember(question: str, answer: str):
21
+ """
22
+ Keep only the last 10 questions. Overwrite oldest.
23
+ """
24
+ memory = _load_memory()
25
+ memory.append({"question": question, "answer": answer})
26
+ if len(memory) > 10:
27
+ memory = memory[-10:]
28
+ _save_memory(memory)
29
+
30
+ def get_last_qa():
31
+ memory = _load_memory()
32
+ if memory:
33
+ return memory[-1]
34
+ return None
35
+
36
+ def get_full_memory():
37
+ return _load_memory()
@@ -0,0 +1,61 @@
1
+ import os
2
+ import sys
3
+
4
+ def assemble_model():
5
+ """
6
+ Find all tinymentor_model_part* packages,
7
+ merge them in order, and generate models/qwen.gguf
8
+ """
9
+ base_dir = os.path.dirname(os.path.abspath(__file__))
10
+ model_dir = os.path.join(base_dir, "models")
11
+ os.makedirs(model_dir, exist_ok=True)
12
+
13
+ qwen_path = os.path.join(model_dir, "qwen.gguf")
14
+ if os.path.exists(qwen_path):
15
+ # We assume if it exists, it's already built.
16
+ return qwen_path
17
+
18
+ print("Assembling model from parts...")
19
+
20
+ parts = []
21
+ i = 1
22
+ while True:
23
+ try:
24
+ mod_name = f"tinymentor_model_part{i}"
25
+ mod = __import__(mod_name)
26
+ part_path = mod.get_part_path()
27
+ parts.append(part_path)
28
+ i += 1
29
+ except ImportError:
30
+ break
31
+
32
+ if not parts:
33
+ raise RuntimeError("No tinymentor-model-part packages found. Please install them.")
34
+
35
+ with open(qwen_path, "wb") as outfile:
36
+ for part_path in parts:
37
+ with open(part_path, "rb") as infile:
38
+ outfile.write(infile.read())
39
+
40
+ print("Model assembly complete.")
41
+ return qwen_path
42
+
43
+ def load_model():
44
+ model_path = assemble_model()
45
+ from llama_cpp import Llama # type: ignore
46
+ # Initialize the model
47
+ llm = Llama(model_path=model_path, verbose=False, n_ctx=2048)
48
+ return llm
49
+
50
+ def generate_response(prompt: str) -> str:
51
+ """
52
+ Load the model using llama-cpp-python and generate a response.
53
+ """
54
+ llm = load_model()
55
+ output = llm(
56
+ prompt,
57
+ max_tokens=512,
58
+ stop=["Q:", "\n\n\n"],
59
+ echo=False
60
+ )
61
+ return output["choices"][0]["text"].strip()
@@ -0,0 +1,61 @@
1
+ import os
2
+
3
+ def search_notes(question: str) -> list:
4
+ """
5
+ Recursively search markdown files in the md/ directory.
6
+ Uses TF-IDF and cosine similarity to return the top 5 matches.
7
+ """
8
+ try:
9
+ from sklearn.feature_extraction.text import TfidfVectorizer
10
+ from sklearn.metrics.pairwise import cosine_similarity
11
+ except ImportError:
12
+ # Fallback if scikit-learn is not installed somehow
13
+ return []
14
+
15
+ base_dir = os.path.join(os.path.dirname(__file__), "md")
16
+ if not os.path.exists(base_dir):
17
+ return []
18
+
19
+ documents = []
20
+ file_paths = []
21
+
22
+ # Collect all md files
23
+ for root, _, files in os.walk(base_dir):
24
+ for file in files:
25
+ if file.endswith(".md"):
26
+ file_path = os.path.join(root, file)
27
+ try:
28
+ with open(file_path, "r", encoding="utf-8") as f:
29
+ content = f.read()
30
+ # Include filename in the document text for scoring
31
+ documents.append(f"{file} {content}")
32
+ file_paths.append(file_path)
33
+ except Exception:
34
+ pass
35
+
36
+ if not documents:
37
+ return []
38
+
39
+ documents.append(question)
40
+
41
+ vectorizer = TfidfVectorizer(stop_words='english')
42
+ try:
43
+ tfidf_matrix = vectorizer.fit_transform(documents)
44
+ except ValueError:
45
+ # e.g., empty vocabulary
46
+ return []
47
+
48
+ # Last row is the question
49
+ cosine_similarities = cosine_similarity(tfidf_matrix[-1], tfidf_matrix[:-1]).flatten()
50
+
51
+ # Get top 5 indices sorted by similarity score
52
+ related_docs_indices = cosine_similarities.argsort()[:-6:-1]
53
+
54
+ top_5 = []
55
+ for idx in related_docs_indices:
56
+ if cosine_similarities[idx] > 0.0: # Only include if there's some similarity
57
+ path = file_paths[idx]
58
+ with open(path, "r", encoding="utf-8") as f:
59
+ top_5.append(f.read())
60
+
61
+ return top_5
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinymentor
3
+ Version: 0.3.0
4
+ Summary: Notebook-first local GenAI coding copilot
5
+ Author-email: tinymentor <tinymentor@example.com>
6
+ Requires-Python: >=3.8
7
+ Requires-Dist: tinymentor-inference>=0.2.62
8
+ Requires-Dist: scikit-learn
9
+ Requires-Dist: tinymentor-model-part1
10
+ Requires-Dist: tinymentor-model-part2
11
+ Requires-Dist: tinymentor-model-part3
12
+ Requires-Dist: tinymentor-model-part4
13
+ Requires-Dist: tinymentor-model-part5
14
+ Requires-Dist: tinymentor-model-part6
15
+ Requires-Dist: tinymentor-model-part7
16
+ Requires-Dist: tinymentor-model-part8
17
+ Requires-Dist: tinymentor-model-part9
18
+ Requires-Dist: tinymentor-model-part10
19
+ Requires-Dist: tinymentor-model-part11
20
+ Requires-Dist: tinymentor-model-part12
21
+ Requires-Dist: tinymentor-model-part13
22
+ Requires-Dist: tinymentor-model-part14
23
+ Requires-Dist: tinymentor-model-part15
24
+ Requires-Dist: tinymentor-model-part16
25
+ Requires-Dist: tinymentor-model-part17
26
+ Requires-Dist: tinymentor-model-part18
27
+ Requires-Dist: tinymentor-model-part19
28
+ Requires-Dist: tinymentor-model-part20
29
+ Requires-Dist: tinymentor-model-part21
30
+ Requires-Dist: tinymentor-model-part22
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/tinymentor/__init__.py
4
+ src/tinymentor/ask.py
5
+ src/tinymentor/classifier.py
6
+ src/tinymentor/errors.py
7
+ src/tinymentor/learning.py
8
+ src/tinymentor/memory.py
9
+ src/tinymentor/model.py
10
+ src/tinymentor/retrieval.py
11
+ src/tinymentor.egg-info/PKG-INFO
12
+ src/tinymentor.egg-info/SOURCES.txt
13
+ src/tinymentor.egg-info/dependency_links.txt
14
+ src/tinymentor.egg-info/requires.txt
15
+ src/tinymentor.egg-info/top_level.txt
@@ -0,0 +1,24 @@
1
+ tinymentor-inference>=0.2.62
2
+ scikit-learn
3
+ tinymentor-model-part1
4
+ tinymentor-model-part2
5
+ tinymentor-model-part3
6
+ tinymentor-model-part4
7
+ tinymentor-model-part5
8
+ tinymentor-model-part6
9
+ tinymentor-model-part7
10
+ tinymentor-model-part8
11
+ tinymentor-model-part9
12
+ tinymentor-model-part10
13
+ tinymentor-model-part11
14
+ tinymentor-model-part12
15
+ tinymentor-model-part13
16
+ tinymentor-model-part14
17
+ tinymentor-model-part15
18
+ tinymentor-model-part16
19
+ tinymentor-model-part17
20
+ tinymentor-model-part18
21
+ tinymentor-model-part19
22
+ tinymentor-model-part20
23
+ tinymentor-model-part21
24
+ tinymentor-model-part22
@@ -0,0 +1 @@
1
+ tinymentor