-
Notifications
You must be signed in to change notification settings - Fork 680
Expand file tree
/
Copy pathllm_client.py
More file actions
122 lines (97 loc) · 3.78 KB
/
llm_client.py
File metadata and controls
122 lines (97 loc) · 3.78 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
"""
Gemini client wrapper used by DocuBot.
Handles:
- Configuring the Gemini client from the GEMINI_API_KEY environment variable
- Naive "generation only" answers over the full docs corpus (Phase 0)
- RAG style answers that use only retrieved snippets (Phase 2)
Experiment with:
- Prompt wording
- Refusal conditions
- How strictly the model is instructed to use only the provided context
"""
import os
from google import genai
# Central place to update the model name if needed.
# You can swap this for a different Gemini model in the future.
GEMINI_MODEL_NAME = "gemma-3-27b-it"
class GeminiClient:
"""
Simple wrapper around the Gemini model.
Usage:
client = GeminiClient()
answer = client.naive_answer_over_full_docs(query, all_text)
# or
answer = client.answer_from_snippets(query, snippets)
"""
def __init__(self):
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise RuntimeError(
"Missing GEMINI_API_KEY environment variable. "
"Set it in your shell or .env file to enable LLM features."
)
self.client = genai.Client(api_key=api_key)
# -----------------------------------------------------------
# Phase 0: naive generation over full docs
# -----------------------------------------------------------
def naive_answer_over_full_docs(self, query, all_text):
# We ignore all_text and send a generic prompt instead
prompt = f"""
You are a documentation assistant.
Answer this developer question: {query}
"""
try:
response = self.client.models.generate_content(
model=GEMINI_MODEL_NAME,
contents=prompt
)
return (response.text or "").strip()
except Exception as e:
return f"Unable to generate an answer. ({type(e).__name__}: {e})"
# -----------------------------------------------------------
# Phase 2: RAG style generation over retrieved snippets
# -----------------------------------------------------------
def answer_from_snippets(self, query, snippets):
"""
Phase 2:
Generate an answer using only the retrieved snippets.
snippets: list of (filename, text) tuples selected by DocuBot.retrieve
The prompt:
- Shows each snippet with its filename
- Instructs the model to rely only on these snippets
- Requires an explicit "I do not know" refusal when needed
"""
if not snippets:
return "I do not know based on the docs I have."
context_blocks = []
for filename, text in snippets:
block = f"File: {filename}\n{text}\n"
context_blocks.append(block)
context = "\n\n".join(context_blocks)
prompt = f"""
You are a cautious documentation assistant helping developers understand a codebase.
You will receive:
- A developer question
- A small set of snippets from project files
Your job:
- Answer the question using only the information in the snippets.
- If the snippets do not provide enough evidence, refuse to guess.
Snippets:
{context}
Developer question:
{query}
Rules:
- Use only the information in the snippets. Do not invent new functions,
endpoints, or configuration values.
- If the snippets are not enough to answer confidently, reply exactly:
"I do not know based on the docs I have."
- When you do answer, briefly mention which files you relied on.
"""
try:
response = self.client.models.generate_content(
model=GEMINI_MODEL_NAME,
contents=prompt
)
return (response.text or "").strip()
except Exception as e:
return f"API error — could not generate answer. ({type(e).__name__}: {e})"