-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpatch_quality_v2.py
More file actions
239 lines (205 loc) · 9.43 KB
/
Copy pathpatch_quality_v2.py
File metadata and controls
239 lines (205 loc) · 9.43 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
"""
patch_quality_v2.py — SynthForge Quality Patch (remaining steps)
==================================================================
System prompt already hardened. This script applies the remaining 5 patches:
1. Adds verify_answer_grounding() — LLM-as-Judge via llama-3.1-8b-instant
2. Hooks judge into search pipeline after generate_answer()
3. Adds _auto_search flag to session state init
4. Suggestion buttons set _auto_search flag
5. Search trigger fires on _auto_search OR button click
Run from C:\\Users\\Ezeking\\SynthForge:
C:\\Users\\Ezeking\\AppData\\Local\\Programs\\Python\\Python311\\python.exe patch_quality_v2.py
"""
import shutil
import sys
from pathlib import Path
APP_PATH = Path(r"C:\Users\Ezeking\hf_space\app.py")
APP_BACKUP = Path(r"C:\Users\Ezeking\hf_space\app.py.bak_quality2")
def patch_once(content: str, find: str, replace: str, label: str) -> str:
if find not in content:
print(f"\n x FAILED [{label}]")
print(f" Anchor: {find[:80]!r}")
sys.exit(1)
print(f" ok {label}")
return content.replace(find, replace, 1)
# =============================================================================
# JUDGE FUNCTION — inserted before fetch_hn_news()
# =============================================================================
JUDGE_FUNCTION = '''
def verify_answer_grounding(
query: str,
answer: str,
retrieved_chunks: list[dict],
) -> str:
"""
LLM-as-Judge: verify the generated answer is grounded in retrieved chunks.
Uses llama-3.1-8b-instant via Groq — fast, free tier.
Appends a grounding warning if unsupported claims detected.
Never blocks — original answer returned on any failure.
Args:
query: Original user query.
answer: Generated answer to verify.
retrieved_chunks: Chunks used during generation.
Returns:
Answer string, optionally annotated with a grounding warning.
"""
import json as _json
groq_key = "".join(os.environ.get("GROQ_API_KEY", "").split())
if not groq_key or not answer.strip():
return answer
# Top 5 chunks, 250 words each
context_parts: list[str] = []
for i, chunk in enumerate(retrieved_chunks[:5], 1):
meta = chunk.get("metadata", {})
src = meta.get("source", "unknown").upper()
words = chunk.get("text", "").split()[:250]
context_parts.append(f"[SOURCE {i}: {src}]\\n{' '.join(words)}")
context = "\\n\\n".join(context_parts)
judge_prompt = (
"You are a hallucination detector for a RAG system about prompt engineering.\\n\\n"
f"RETRIEVED SOURCES:\\n{context}\\n\\n"
f"ANSWER TO CHECK (first 500 words):\\n{' '.join(answer.split()[:500])}\\n\\n"
"Find specific factual claims in the answer NOT supported by the sources. "
"Focus on: benchmark numbers, paper titles, percentages, techniques stated as fact.\\n\\n"
"Reply with ONLY valid JSON:\\n"
'{"grounded": true_or_false, "issues": ["unsupported claim 1", "etc"]}'
)
try:
resp = requests.post(
GROQ_API_URL,
headers={"Authorization": f"Bearer {groq_key}",
"Content-Type": "application/json"},
json={
"model": "llama-3.1-8b-instant",
"messages": [{"role": "user", "content": judge_prompt}],
"max_tokens": 300,
"temperature": 0.0,
},
timeout=12,
)
resp.raise_for_status()
raw = resp.json()["choices"][0]["message"]["content"].strip()
raw = raw.replace("```json", "").replace("```", "").strip()
result = _json.loads(raw)
issues = [str(i).strip() for i in result.get("issues", []) if str(i).strip()]
grounded = result.get("grounded", True)
if not grounded and issues:
issue_text = "; ".join(f"*{iss}*" for iss in issues[:3])
warning = (
"\\n\\n---\\n"
"\u26a0\ufe0f **Grounding Verification:** The following claims could not be "
"fully verified against retrieved sources and may require independent "
"confirmation: " + issue_text
)
logger.info("Judge flagged %d unsupported claim(s).", len(issues))
return answer + warning
logger.debug("Judge: answer fully grounded.")
return answer
except Exception as exc:
logger.debug("Judge skipped (non-blocking): %s", exc)
return answer
'''
def main() -> None:
if not APP_PATH.exists():
print(f"ERROR: {APP_PATH} not found.")
sys.exit(1)
print(f"\nSynthForge Quality Patch v2 (remaining 5 patches)")
print(f"Target : {APP_PATH}\n")
raw = APP_PATH.read_bytes()
content = raw.decode("utf-8").replace("\r\n", "\n")
shutil.copy(APP_PATH, APP_BACKUP)
print(f"Backup : {APP_BACKUP.name}\n")
print("System prompt: already hardened — skipping")
print("\nApplying remaining patches:")
# ------------------------------------------------------------------
# Patch 1: Insert verify_answer_grounding() before fetch_hn_news()
# ------------------------------------------------------------------
if "def verify_answer_grounding(" in content:
print(" ok verify_answer_grounding() already present — skipping")
else:
content = patch_once(
content,
"@st.cache_data(ttl=300)\ndef fetch_hn_news()",
JUDGE_FUNCTION + "@st.cache_data(ttl=300)\ndef fetch_hn_news()",
"verify_answer_grounding() inserted before fetch_hn_news()",
)
# ------------------------------------------------------------------
# Patch 2: Hook judge into pipeline after generate_answer()
# ------------------------------------------------------------------
if "verify_answer_grounding(query.strip(), answer, results)" in content:
print(" ok Judge already hooked into pipeline — skipping")
else:
content = patch_once(
content,
" answer = generate_answer(query.strip(), results, file_context)\n"
"\n"
" elapsed = time.time() - t0",
" answer = generate_answer(query.strip(), results, file_context)\n"
" answer = verify_answer_grounding(query.strip(), answer, results)\n"
"\n"
" elapsed = time.time() - t0",
"Judge hooked into search pipeline",
)
# ------------------------------------------------------------------
# Patch 3: Add _auto_search to session state init
# ------------------------------------------------------------------
if '"_auto_search"' in content:
print(" ok _auto_search already in session state — skipping")
else:
content = patch_once(
content,
' ("last_query", None), ("last_had_file", False),\n'
']:\n'
' if key not in st.session_state:\n'
' st.session_state[key] = default',
' ("last_query", None), ("last_had_file", False), ("_auto_search", False),\n'
']:\n'
' if key not in st.session_state:\n'
' st.session_state[key] = default',
"_auto_search flag added to session state init",
)
# ------------------------------------------------------------------
# Patch 4: Suggestion buttons set _auto_search flag
# ------------------------------------------------------------------
SUGG_OLD = (
' if st.button(f"\u2192 {s}", key=f"sg_{i}", use_container_width=True):\n'
' st.session_state.update({"query_input": s, "suggestions": []})\n'
' st.rerun()'
)
SUGG_NEW = (
' if st.button(f"\u2192 {s}", key=f"sg_{i}", use_container_width=True):\n'
' st.session_state.update({"query_input": s, "suggestions": [], "_auto_search": True})\n'
' st.rerun()'
)
if '"_auto_search": True' in content:
print(" ok Suggestion buttons already set _auto_search — skipping")
else:
content = patch_once(content, SUGG_OLD, SUGG_NEW,
"Suggestion buttons set _auto_search flag")
# ------------------------------------------------------------------
# Patch 5: Search trigger respects _auto_search
# ------------------------------------------------------------------
if "_auto_search = st.session_state.get" in content:
print(" ok Search trigger already respects _auto_search — skipping")
else:
content = patch_once(
content,
"if search_clicked and query.strip():",
'_auto_search = st.session_state.get("_auto_search", False)\n'
'if _auto_search:\n'
' st.session_state["_auto_search"] = False\n'
'if (search_clicked or _auto_search) and query.strip():',
"Search trigger respects _auto_search flag",
)
# Restore CRLF if original had it
if b"\r\n" in raw:
content = content.replace("\n", "\r\n")
APP_PATH.write_bytes(content.encode("utf-8"))
print(f"\nAll patches applied. {APP_PATH.name} updated.")
print("\nDeploy:")
print(r" cd C:\Users\Ezeking\hf_space")
print(r" git add app.py")
print(r' git commit -m "Quality: LLM-as-Judge, auto-submit suggestions"')
print(r" git push")
if __name__ == "__main__":
main()