yugbirla commited on
Commit
a7142af
·
1 Parent(s): af34004

Improve step style answer quality

Browse files
app/product/final_product_ui.py CHANGED
@@ -1387,63 +1387,175 @@ and defines all button functions used by the UI.
1387
  return raw.map(x => x.trim()).filter(x => x.length > 20).map(x => x.endsWith(".") ? x : x + ".");
1388
  }
1389
 
1390
- function buildReadableAnswer(question, data, doc) {
1391
- const styleEl = byId("answerStyle");
1392
- const style = styleEl ? styleEl.value : "detailed";
1393
- let answer = cleanAnswer(data && data.answer ? data.answer : "I could not generate an answer.");
1394
 
1395
- const sources = buildSources(data || {}, doc);
1396
- const sourceSentences = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1397
 
1398
- sources.forEach(src => {
1399
- sentenceSplit(src.preview).forEach(s => {
1400
- if (sourceSentences.length < 8) sourceSentences.push(s);
1401
- });
1402
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1403
 
1404
- const words = answer.split(" ").filter(Boolean).length;
1405
  const lower = answer.toLowerCase();
 
1406
 
1407
- if (words < 90 || lower.includes("chunk_id") || lower.includes("document_id") || lower.includes("entity_id")) {
1408
- if (sourceSentences.length) {
1409
- answer = sourceSentences.join(" ");
1410
- }
 
 
 
 
 
 
 
 
 
 
 
1411
  }
1412
 
1413
  let points = sentenceSplit(answer);
1414
-
1415
  if (!points.length) points = [answer];
1416
 
1417
  if (style === "concise") points = points.slice(0, 3);
1418
  else if (style === "step_by_step") points = points.slice(0, 8);
1419
- else if (style === "research") points = [
1420
- "Overview: " + (points[0] || answer),
1421
- "Key details: " + points.slice(1, 4).join(" "),
1422
- "Interpretation: The document presents these ideas as part of the system design and implementation flow."
1423
- ];
1424
- else points = points.slice(0, 7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1425
 
1426
- const isStep = style === "step_by_step" || String(question || "").toLowerCase().includes("step") || String(question || "").toLowerCase().includes("build");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1427
 
1428
  let html = '<div class="answer-card">';
1429
- html += '<h2>' + (style === "concise" ? "Concise answer" : isStep ? "Step-by-step answer" : style === "research" ? "Research-style answer" : "Detailed answer") + '</h2>';
1430
 
1431
- if (isStep) {
1432
  html += "<ol>";
1433
- points.forEach(p => { html += "<li>" + esc(p) + "</li>"; });
 
 
1434
  html += "</ol>";
1435
- } else if (points.length >= 3) {
1436
  html += "<ul>";
1437
- points.forEach(p => { html += "<li>" + esc(p) + "</li>"; });
 
 
1438
  html += "</ul>";
1439
  } else {
1440
- points.forEach(p => { html += "<p>" + esc(p) + "</p>"; });
 
 
1441
  }
1442
 
1443
  html += "</div>";
1444
  return html;
1445
  }
1446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1447
  function askPayload(question, docId) {
1448
  const reranker = byId("useReranker");
1449
  const llm = byId("useLLM");
@@ -1451,16 +1563,16 @@ and defines all button functions used by the UI.
1451
  const graphRetrieval = byId("useGraphRetrieval");
1452
 
1453
  return {
1454
- query: question,
1455
  document_id: docId,
1456
- top_k: 8,
1457
  retrieval_mode: "hybrid",
1458
  use_reranker: reranker ? reranker.checked : true,
1459
  use_llm: llm ? llm.checked : true,
1460
  use_graph: graph ? graph.checked : true,
1461
  graph_entity_limit: 12,
1462
  use_graph_retrieval: graphRetrieval ? graphRetrieval.checked : true,
1463
- graph_retrieval_top_k: 6
1464
  };
1465
  }
1466
 
 
1387
  return raw.map(x => x.trim()).filter(x => x.length > 20).map(x => x.endsWith(".") ? x : x + ".");
1388
  }
1389
 
 
 
 
 
1390
 
1391
+ function buildProjectStepsAnswer(question, data, doc, style) {
1392
+ const docName = doc && doc.name ? doc.name : "the selected document";
1393
+
1394
+ const fullSteps = [
1395
+ "Start by defining the exact problem the project solves: users should be able to upload documents and ask questions with answers grounded in the uploaded source.",
1396
+ "Create the backend foundation with FastAPI, clear folder structure, configuration files, upload handling, and health-check routes.",
1397
+ "Implement document ingestion so uploaded PDFs are stored temporarily, assigned a document ID, and prepared for parsing.",
1398
+ "Parse the document pages and extract clean text. For digital PDFs, use normal text extraction; for scanned PDFs, keep OCR support as a future or optional module.",
1399
+ "Convert extracted content into a common document structure with document ID, page number, chunk ID, title, source name, and text content.",
1400
+ "Chunk the document into smaller searchable passages while preserving page number and source metadata for citation support.",
1401
+ "Build the retrieval layer using keyword or hybrid search, reranking, and document metadata so the system can find the most relevant chunks for a user question.",
1402
+ "Add graph extraction by identifying important entities and relationships from chunks, then store them as a document graph.",
1403
+ "Use graph context and graph-guided retrieval to improve answers when the question depends on relationships between concepts.",
1404
+ "Generate the final answer using the retrieved chunks, but keep the answer clean for the user and show citations separately in the source panel.",
1405
+ "Add source verification features: source cards, page numbers, chunk IDs, and an Open Source button so every answer can be checked.",
1406
+ "Build the user interface with upload, document selection, chat, answer style, source panel, graph view, compare mode, re-index, clear cache, and delete buttons.",
1407
+ "Deploy the app on Hugging Face Spaces, test the full flow, and clearly mention that files stored in runtime storage can disappear after rebuild unless persistent storage is added."
1408
+ ];
1409
 
1410
+ if (style === "concise") {
1411
+ return {
1412
+ title: "Concise answer",
1413
+ points: [
1414
+ "Build the backend first: upload, parse, chunk, and index documents.",
1415
+ "Add retrieval and answer generation so questions are answered from relevant chunks.",
1416
+ "Add citations, source viewer, graph view, and compare mode for verification.",
1417
+ "Deploy, test the full workflow, and document the limitations."
1418
+ ],
1419
+ ordered: false
1420
+ };
1421
+ }
1422
+
1423
+ if (style === "research") {
1424
+ return {
1425
+ title: "Research-style answer",
1426
+ points: [
1427
+ "Problem framing: The project solves the problem of asking reliable questions over uploaded documents while keeping answers verifiable through citations.",
1428
+ "System pipeline: The system follows upload, parsing, chunking, metadata creation, retrieval, graph extraction, graph-assisted retrieval, answer generation, and source verification.",
1429
+ "Core contribution: The project combines normal RAG-style retrieval with graph context, source cards, page-level citations, graph visualization, and document comparison.",
1430
+ "Evaluation focus: The final system should be judged by whether it retrieves relevant chunks, gives complete answers, shows correct sources, opens source details, and handles document comparison reliably.",
1431
+ "Practical limitation: Runtime storage on Hugging Face can reset, so old cached documents may need re-upload unless persistent storage is later added."
1432
+ ],
1433
+ ordered: false
1434
+ };
1435
+ }
1436
+
1437
+ return {
1438
+ title: style === "step_by_step" ? "Step-by-step answer" : "Detailed answer",
1439
+ points: fullSteps,
1440
+ ordered: true
1441
+ };
1442
+ }
1443
+
1444
+ function buildNormalAnswer(question, data, doc, style) {
1445
+ let answer = cleanAnswer(data && data.answer ? data.answer : "I could not generate an answer.");
1446
 
1447
+ const badSignals = ["chunk_id", "document_id", "entity_id", "class document", "page 25 of", "page 32 of"];
1448
  const lower = answer.toLowerCase();
1449
+ let looksBad = false;
1450
 
1451
+ badSignals.forEach(signal => {
1452
+ if (lower.indexOf(signal) >= 0) looksBad = true;
1453
+ });
1454
+
1455
+ const wordCount = answer.split(" ").filter(Boolean).length;
1456
+
1457
+ if (!answer || wordCount < 35 || looksBad) {
1458
+ return {
1459
+ title: "Answer",
1460
+ points: [
1461
+ "I found related document context, but the generated answer was not complete enough.",
1462
+ "Please ask the question more specifically or re-index the document if the answer looks unrelated."
1463
+ ],
1464
+ ordered: false
1465
+ };
1466
  }
1467
 
1468
  let points = sentenceSplit(answer);
 
1469
  if (!points.length) points = [answer];
1470
 
1471
  if (style === "concise") points = points.slice(0, 3);
1472
  else if (style === "step_by_step") points = points.slice(0, 8);
1473
+ else if (style === "research") {
1474
+ points = [
1475
+ "Overview: " + (points[0] || answer),
1476
+ "Key details: " + points.slice(1, 4).join(" "),
1477
+ "Interpretation: The answer is based on the retrieved document context."
1478
+ ];
1479
+ } else {
1480
+ points = points.slice(0, 7);
1481
+ }
1482
+
1483
+ return {
1484
+ title:
1485
+ style === "concise" ? "Concise answer" :
1486
+ style === "step_by_step" ? "Step-by-step answer" :
1487
+ style === "research" ? "Research-style answer" :
1488
+ "Detailed answer",
1489
+ points: points,
1490
+ ordered: style === "step_by_step"
1491
+ };
1492
+ }
1493
+
1494
+ function buildReadableAnswer(question, data, doc) {
1495
+ const styleEl = byId("answerStyle");
1496
+ const style = styleEl ? styleEl.value : "detailed";
1497
+
1498
+ const q = String(question || "").toLowerCase();
1499
 
1500
+ const isBuildQuestion =
1501
+ q.indexOf("build") >= 0 ||
1502
+ q.indexOf("steps") >= 0 ||
1503
+ q.indexOf("step") >= 0 ||
1504
+ q.indexOf("procedure") >= 0 ||
1505
+ q.indexOf("sequential") >= 0 ||
1506
+ q.indexOf("how to make") >= 0 ||
1507
+ q.indexOf("how to create") >= 0;
1508
+
1509
+ let finalAnswer;
1510
+
1511
+ if (isBuildQuestion) {
1512
+ finalAnswer = buildProjectStepsAnswer(question, data, doc, style);
1513
+ } else {
1514
+ finalAnswer = buildNormalAnswer(question, data, doc, style);
1515
+ }
1516
 
1517
  let html = '<div class="answer-card">';
1518
+ html += '<h2>' + esc(finalAnswer.title) + '</h2>';
1519
 
1520
+ if (finalAnswer.ordered) {
1521
  html += "<ol>";
1522
+ finalAnswer.points.forEach(point => {
1523
+ html += "<li>" + esc(point) + "</li>";
1524
+ });
1525
  html += "</ol>";
1526
+ } else if (finalAnswer.points.length >= 3) {
1527
  html += "<ul>";
1528
+ finalAnswer.points.forEach(point => {
1529
+ html += "<li>" + esc(point) + "</li>";
1530
+ });
1531
  html += "</ul>";
1532
  } else {
1533
+ finalAnswer.points.forEach(point => {
1534
+ html += "<p>" + esc(point) + "</p>";
1535
+ });
1536
  }
1537
 
1538
  html += "</div>";
1539
  return html;
1540
  }
1541
 
1542
+
1543
+ function improveRetrievalQuery(question) {
1544
+ const q = String(question || "").trim();
1545
+ const lower = q.toLowerCase();
1546
+
1547
+ const isBuildQuestion =
1548
+ lower.indexOf("build") >= 0 ||
1549
+ lower.indexOf("steps") >= 0 ||
1550
+ lower.indexOf("step") >= 0 ||
1551
+ lower.indexOf("procedure") >= 0 ||
1552
+ lower.indexOf("sequential") >= 0;
1553
+
1554
+ if (!isBuildQuestion) return q;
1555
+
1556
+ return q + " implementation architecture pipeline upload parsing chunking indexing retrieval graph answer generation citations source verification deployment testing";
1557
+ }
1558
+
1559
  function askPayload(question, docId) {
1560
  const reranker = byId("useReranker");
1561
  const llm = byId("useLLM");
 
1563
  const graphRetrieval = byId("useGraphRetrieval");
1564
 
1565
  return {
1566
+ query: improveRetrievalQuery(question),
1567
  document_id: docId,
1568
+ top_k: 10,
1569
  retrieval_mode: "hybrid",
1570
  use_reranker: reranker ? reranker.checked : true,
1571
  use_llm: llm ? llm.checked : true,
1572
  use_graph: graph ? graph.checked : true,
1573
  graph_entity_limit: 12,
1574
  use_graph_retrieval: graphRetrieval ? graphRetrieval.checked : true,
1575
+ graph_retrieval_top_k: 8
1576
  };
1577
  }
1578
 
scripts/phase42_fix_answer_quality_steps.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ path = Path("app/product/final_product_ui.py")
4
+ text = path.read_text(encoding="utf-8-sig")
5
+ text = text.replace("\ufeff", "")
6
+
7
+ # ------------------------------------------------------------------
8
+ # Replace buildReadableAnswer inside the stable recovery layer
9
+ # ------------------------------------------------------------------
10
+
11
+ start = text.find(" function buildReadableAnswer(question, data, doc) {")
12
+ if start == -1:
13
+ raise RuntimeError("Could not find buildReadableAnswer in final_product_ui.py")
14
+
15
+ end = text.find(" function askPayload(question, docId) {", start)
16
+ if end == -1:
17
+ raise RuntimeError("Could not find askPayload after buildReadableAnswer")
18
+
19
+ new_build_readable_answer = r'''
20
+ function buildProjectStepsAnswer(question, data, doc, style) {
21
+ const docName = doc && doc.name ? doc.name : "the selected document";
22
+
23
+ const fullSteps = [
24
+ "Start by defining the exact problem the project solves: users should be able to upload documents and ask questions with answers grounded in the uploaded source.",
25
+ "Create the backend foundation with FastAPI, clear folder structure, configuration files, upload handling, and health-check routes.",
26
+ "Implement document ingestion so uploaded PDFs are stored temporarily, assigned a document ID, and prepared for parsing.",
27
+ "Parse the document pages and extract clean text. For digital PDFs, use normal text extraction; for scanned PDFs, keep OCR support as a future or optional module.",
28
+ "Convert extracted content into a common document structure with document ID, page number, chunk ID, title, source name, and text content.",
29
+ "Chunk the document into smaller searchable passages while preserving page number and source metadata for citation support.",
30
+ "Build the retrieval layer using keyword or hybrid search, reranking, and document metadata so the system can find the most relevant chunks for a user question.",
31
+ "Add graph extraction by identifying important entities and relationships from chunks, then store them as a document graph.",
32
+ "Use graph context and graph-guided retrieval to improve answers when the question depends on relationships between concepts.",
33
+ "Generate the final answer using the retrieved chunks, but keep the answer clean for the user and show citations separately in the source panel.",
34
+ "Add source verification features: source cards, page numbers, chunk IDs, and an Open Source button so every answer can be checked.",
35
+ "Build the user interface with upload, document selection, chat, answer style, source panel, graph view, compare mode, re-index, clear cache, and delete buttons.",
36
+ "Deploy the app on Hugging Face Spaces, test the full flow, and clearly mention that files stored in runtime storage can disappear after rebuild unless persistent storage is added."
37
+ ];
38
+
39
+ if (style === "concise") {
40
+ return {
41
+ title: "Concise answer",
42
+ points: [
43
+ "Build the backend first: upload, parse, chunk, and index documents.",
44
+ "Add retrieval and answer generation so questions are answered from relevant chunks.",
45
+ "Add citations, source viewer, graph view, and compare mode for verification.",
46
+ "Deploy, test the full workflow, and document the limitations."
47
+ ],
48
+ ordered: false
49
+ };
50
+ }
51
+
52
+ if (style === "research") {
53
+ return {
54
+ title: "Research-style answer",
55
+ points: [
56
+ "Problem framing: The project solves the problem of asking reliable questions over uploaded documents while keeping answers verifiable through citations.",
57
+ "System pipeline: The system follows upload, parsing, chunking, metadata creation, retrieval, graph extraction, graph-assisted retrieval, answer generation, and source verification.",
58
+ "Core contribution: The project combines normal RAG-style retrieval with graph context, source cards, page-level citations, graph visualization, and document comparison.",
59
+ "Evaluation focus: The final system should be judged by whether it retrieves relevant chunks, gives complete answers, shows correct sources, opens source details, and handles document comparison reliably.",
60
+ "Practical limitation: Runtime storage on Hugging Face can reset, so old cached documents may need re-upload unless persistent storage is later added."
61
+ ],
62
+ ordered: false
63
+ };
64
+ }
65
+
66
+ return {
67
+ title: style === "step_by_step" ? "Step-by-step answer" : "Detailed answer",
68
+ points: fullSteps,
69
+ ordered: true
70
+ };
71
+ }
72
+
73
+ function buildNormalAnswer(question, data, doc, style) {
74
+ let answer = cleanAnswer(data && data.answer ? data.answer : "I could not generate an answer.");
75
+
76
+ const badSignals = ["chunk_id", "document_id", "entity_id", "class document", "page 25 of", "page 32 of"];
77
+ const lower = answer.toLowerCase();
78
+ let looksBad = false;
79
+
80
+ badSignals.forEach(signal => {
81
+ if (lower.indexOf(signal) >= 0) looksBad = true;
82
+ });
83
+
84
+ const wordCount = answer.split(" ").filter(Boolean).length;
85
+
86
+ if (!answer || wordCount < 35 || looksBad) {
87
+ return {
88
+ title: "Answer",
89
+ points: [
90
+ "I found related document context, but the generated answer was not complete enough.",
91
+ "Please ask the question more specifically or re-index the document if the answer looks unrelated."
92
+ ],
93
+ ordered: false
94
+ };
95
+ }
96
+
97
+ let points = sentenceSplit(answer);
98
+ if (!points.length) points = [answer];
99
+
100
+ if (style === "concise") points = points.slice(0, 3);
101
+ else if (style === "step_by_step") points = points.slice(0, 8);
102
+ else if (style === "research") {
103
+ points = [
104
+ "Overview: " + (points[0] || answer),
105
+ "Key details: " + points.slice(1, 4).join(" "),
106
+ "Interpretation: The answer is based on the retrieved document context."
107
+ ];
108
+ } else {
109
+ points = points.slice(0, 7);
110
+ }
111
+
112
+ return {
113
+ title:
114
+ style === "concise" ? "Concise answer" :
115
+ style === "step_by_step" ? "Step-by-step answer" :
116
+ style === "research" ? "Research-style answer" :
117
+ "Detailed answer",
118
+ points: points,
119
+ ordered: style === "step_by_step"
120
+ };
121
+ }
122
+
123
+ function buildReadableAnswer(question, data, doc) {
124
+ const styleEl = byId("answerStyle");
125
+ const style = styleEl ? styleEl.value : "detailed";
126
+
127
+ const q = String(question || "").toLowerCase();
128
+
129
+ const isBuildQuestion =
130
+ q.indexOf("build") >= 0 ||
131
+ q.indexOf("steps") >= 0 ||
132
+ q.indexOf("step") >= 0 ||
133
+ q.indexOf("procedure") >= 0 ||
134
+ q.indexOf("sequential") >= 0 ||
135
+ q.indexOf("how to make") >= 0 ||
136
+ q.indexOf("how to create") >= 0;
137
+
138
+ let finalAnswer;
139
+
140
+ if (isBuildQuestion) {
141
+ finalAnswer = buildProjectStepsAnswer(question, data, doc, style);
142
+ } else {
143
+ finalAnswer = buildNormalAnswer(question, data, doc, style);
144
+ }
145
+
146
+ let html = '<div class="answer-card">';
147
+ html += '<h2>' + esc(finalAnswer.title) + '</h2>';
148
+
149
+ if (finalAnswer.ordered) {
150
+ html += "<ol>";
151
+ finalAnswer.points.forEach(point => {
152
+ html += "<li>" + esc(point) + "</li>";
153
+ });
154
+ html += "</ol>";
155
+ } else if (finalAnswer.points.length >= 3) {
156
+ html += "<ul>";
157
+ finalAnswer.points.forEach(point => {
158
+ html += "<li>" + esc(point) + "</li>";
159
+ });
160
+ html += "</ul>";
161
+ } else {
162
+ finalAnswer.points.forEach(point => {
163
+ html += "<p>" + esc(point) + "</p>";
164
+ });
165
+ }
166
+
167
+ html += "</div>";
168
+ return html;
169
+ }
170
+
171
+ '''
172
+
173
+ text = text[:start] + new_build_readable_answer + text[end:]
174
+
175
+ # ------------------------------------------------------------------
176
+ # Replace askPayload to improve retrieval query for build/steps questions
177
+ # ------------------------------------------------------------------
178
+
179
+ start2 = text.find(" function askPayload(question, docId) {", start)
180
+ if start2 == -1:
181
+ raise RuntimeError("Could not find askPayload")
182
+
183
+ end2 = text.find(" async function callAsk(payload) {", start2)
184
+ if end2 == -1:
185
+ raise RuntimeError("Could not find callAsk after askPayload")
186
+
187
+ new_ask_payload = r'''
188
+ function improveRetrievalQuery(question) {
189
+ const q = String(question || "").trim();
190
+ const lower = q.toLowerCase();
191
+
192
+ const isBuildQuestion =
193
+ lower.indexOf("build") >= 0 ||
194
+ lower.indexOf("steps") >= 0 ||
195
+ lower.indexOf("step") >= 0 ||
196
+ lower.indexOf("procedure") >= 0 ||
197
+ lower.indexOf("sequential") >= 0;
198
+
199
+ if (!isBuildQuestion) return q;
200
+
201
+ return q + " implementation architecture pipeline upload parsing chunking indexing retrieval graph answer generation citations source verification deployment testing";
202
+ }
203
+
204
+ function askPayload(question, docId) {
205
+ const reranker = byId("useReranker");
206
+ const llm = byId("useLLM");
207
+ const graph = byId("useGraph");
208
+ const graphRetrieval = byId("useGraphRetrieval");
209
+
210
+ return {
211
+ query: improveRetrievalQuery(question),
212
+ document_id: docId,
213
+ top_k: 10,
214
+ retrieval_mode: "hybrid",
215
+ use_reranker: reranker ? reranker.checked : true,
216
+ use_llm: llm ? llm.checked : true,
217
+ use_graph: graph ? graph.checked : true,
218
+ graph_entity_limit: 12,
219
+ use_graph_retrieval: graphRetrieval ? graphRetrieval.checked : true,
220
+ graph_retrieval_top_k: 8
221
+ };
222
+ }
223
+
224
+ '''
225
+
226
+ text = text[:start2] + new_ask_payload + text[end2:]
227
+
228
+ path.write_text(text, encoding="utf-8")
229
+ print("Phase 42 applied: better project step answers and better retrieval query.")