-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathdocubot.py
More file actions
145 lines (119 loc) · 4.68 KB
/
Copy pathdocubot.py
File metadata and controls
145 lines (119 loc) · 4.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""
Core DocuBot class responsible for:
- Loading documents from the docs/ folder
- Building a simple retrieval index (Phase 1)
- Retrieving relevant snippets (Phase 1)
- Supporting retrieval only answers
- Supporting RAG answers when paired with Gemini (Phase 2)
"""
import os
import glob
class DocuBot:
def __init__(self, docs_folder="docs", llm_client=None):
"""
docs_folder: directory containing project documentation files
llm_client: optional Gemini client for LLM based answers
"""
self.docs_folder = docs_folder
self.llm_client = llm_client
# Load documents into memory
self.documents = self.load_documents() # List of (filename, text)
# Build a retrieval index (implemented in Phase 1)
self.index = self.build_index(self.documents)
# -----------------------------------------------------------
# Document Loading
# -----------------------------------------------------------
def load_documents(self):
"""
Loads all .md and .txt files inside docs_folder.
Returns a list of tuples: (filename, text)
"""
docs = []
pattern = os.path.join(self.docs_folder, "*.*")
for path in glob.glob(pattern):
if path.endswith(".md") or path.endswith(".txt"):
with open(path, "r", encoding="utf8") as f:
text = f.read()
filename = os.path.basename(path)
docs.append((filename, text))
return docs
# -----------------------------------------------------------
# Index Construction (Phase 1)
# -----------------------------------------------------------
def build_index(self, documents):
"""
TODO (Phase 1):
Build a tiny inverted index mapping lowercase words to the documents
they appear in.
Example structure:
{
"token": ["AUTH.md", "API_REFERENCE.md"],
"database": ["DATABASE.md"]
}
Keep this simple: split on whitespace, lowercase tokens,
ignore punctuation if needed.
"""
index = {}
# TODO: implement simple indexing
return index
# -----------------------------------------------------------
# Scoring and Retrieval (Phase 1)
# -----------------------------------------------------------
def score_document(self, query, text):
"""
TODO (Phase 1):
Return a simple relevance score for how well the text matches the query.
Suggested baseline:
- Convert query into lowercase words
- Count how many appear in the text
- Return the count as the score
"""
# TODO: implement scoring
return 0
def retrieve(self, query, top_k=3):
"""
TODO (Phase 1):
Use the index and scoring function to select top_k relevant document snippets.
Return a list of (filename, text) sorted by score descending.
"""
results = []
# TODO: implement retrieval logic
return results[:top_k]
# -----------------------------------------------------------
# Answering Modes
# -----------------------------------------------------------
def answer_retrieval_only(self, query, top_k=3):
"""
Phase 1 retrieval only mode.
Returns raw snippets and filenames with no LLM involved.
"""
snippets = self.retrieve(query, top_k=top_k)
if not snippets:
return "I do not know based on these docs."
formatted = []
for filename, text in snippets:
formatted.append(f"[{filename}]\n{text}\n")
return "\n---\n".join(formatted)
def answer_rag(self, query, top_k=3):
"""
Phase 2 RAG mode.
Uses student retrieval to select snippets, then asks Gemini
to generate an answer using only those snippets.
"""
if self.llm_client is None:
raise RuntimeError(
"RAG mode requires an LLM client. Provide a GeminiClient instance."
)
snippets = self.retrieve(query, top_k=top_k)
if not snippets:
return "I do not know based on these docs."
return self.llm_client.answer_from_snippets(query, snippets)
# -----------------------------------------------------------
# Bonus Helper: concatenated docs for naive generation mode
# -----------------------------------------------------------
def full_corpus_text(self):
"""
Returns all documents concatenated into a single string.
This is used in Phase 0 for naive 'generation only' baselines.
"""
return "\n\n".join(text for _, text in self.documents)