kaburia commited on
Commit
9f75f1e
·
1 Parent(s): daa29bf

reverting back

Browse files
Files changed (1) hide show
  1. utils/model_generation.py +43 -232
utils/model_generation.py CHANGED
@@ -1,76 +1,32 @@
1
  import json
2
  import requests
3
- from typing import List, Dict, Any, Union, Optional
4
  import time
5
  import numpy as np
 
6
  import os
7
- import re
8
-
9
- # ---------------------------
10
- # Comparison & Rendering Config
11
- # ---------------------------
12
- COMPARISON_CONFIG = {
13
- "trigger_keywords": [
14
- "compare", "comparison", "vs", "versus", "delta", "differences",
15
- "benchmark", "matrix", "table", "side-by-side", "contrast", "diff", "gap analysis"
16
- ],
17
- "default_dimensions": [
18
- "Scope/Entities", "Definitions", "Obligations/Controls", "Exemptions/Thresholds",
19
- "Deadlines/Effective Dates", "Reporting/Recordkeeping",
20
- "Enforcement Authority", "Penalties/Sanctions", "Cross-Border/Transfer",
21
- "Data Retention", "Audits/Inspections"
22
- ],
23
- "unknown_token": "Not stated in sources",
24
- "default_render_format": "markdown", # 'markdown' | 'csv' | 'json'
25
- }
26
 
27
- VERBOSITY_HINT = "Target depth: medium-to-long. Use complete sentences and adequate context; avoid terse bullet-only outputs."
 
28
 
29
 
30
  PROMPT_TEMPLATES = {
31
  "verbatim_sentiment": {
32
  "system": (
33
- "You are a compliance-grade policy analyst assistant. Prime directive: be faithful to the provided sources. "
34
- "Do NOT speculate. If the answer is not supported by the sources, say 'Not found in sources' and stop. "
35
- "Every non-trivial claim MUST be grounded with an inline citation in the form (filename p.X). "
36
- "Prefer 'unknown/not stated' over guessing. "
37
- "Follow this Grounding Protocol before answering: (1) read Context Sources; (2) extract exact quotes; "
38
- "(3) map each assertion to a citation; (4) list gaps and unknowns. "
39
- "Write in a direct, corporate tone; skeptical and gap-seeking. "
40
- "Avoid hallucinations. Base everything strictly on the content provided. "
41
- f"{VERBOSITY_HINT} "
42
- "If sentiment or coherence inputs are disabled or empty, omit those sections entirely—do not mention they were omitted. "
43
- "If comparison triggers are present, begin with a table-first comparative section as specified."
44
  ),
45
  "user_template": """
46
  Query: {query}
47
 
48
- Deliverables (use the exact section headers below; omit any section whose input is empty/disabled):
49
- 1) Quoted Policy Excerpts
50
- - Quote the provided text and append citations like (filename p.X). Group by subtopic ordered.
51
- 2) Evidence-Backed Findings
52
- - Paraphrase what the excerpts establish. Each bullet ends with a citation (filename p.X).
53
- 3) Sentiment Summary
54
- - Using the Sentiment JSON, explain tone, gaps, penalties, and enforcement clarity in plain English. Do not invent fields that aren't present.
55
- 4) Coherence Assessment
56
- - From the coherence report: state on-topic vs off-topic; call out which sections were coherent, off-topic, or repeated.
57
- 5) Risks & Unknowns
58
- - Explicitly list ambiguities, missing definitions, or conflicts across sources.
59
- 6) Compliance Implications
60
- - Concrete next steps or checks a compliance team should run based strictly on the sources.
61
-
62
- # Comparative Table (render only if comparison triggers are present or {force_table}=True)
63
- - Render format: {render_format} (default markdown).
64
- - Columns (strict): Dimension | Document | Provision (Summary) | Deadlines/Dates | Enforcement/Penalties | Citation | Notes/Risks
65
- - Dimensions to use: {dimensions_hint_or_default}
66
- - Populate rows for each (Dimension × Document) where evidence exists.
67
- - Every non-trivial cell ends with a citation (filename p.X). Unknowns = '{unknown_token}'.
68
- - Sort by Dimension, then Document. If only one document exists, produce a single-column table (no invented comparisons).
69
-
70
- Constraints:
71
- - No external knowledge. No speculation. If a user ask is outside the sources, state 'Not found in sources.'
72
- - Each substantive statement has a citation.
73
- - Avoid quotes unless legally binding language is essential.
74
 
75
  Topic hint: {topic_hint}
76
 
@@ -88,29 +44,12 @@ Context Sources:
88
  "abstractive_summary": {
89
  "system": (
90
  "You are a policy analyst summarizing government documents for a general audience. "
91
- "Faithfulness is mandatory: paraphrase only what is supported by the sources and cite key claims inline (filename p.X). "
92
- "Avoid quotes unless legally binding language is essential. "
93
- f"{VERBOSITY_HINT} "
94
- "If critical info is absent, say 'Not found in sources'—do not infer. "
95
- "If comparison triggers are present, begin with a table-first comparative section as specified."
96
  ),
97
  "user_template": """Query: {query}
98
 
99
- Write a comprehensive, plain-language summary with these sections:
100
- - What It Covers (scope, entities, timelines) [cite]
101
- - Key Requirements & Controls (what must be done) [cite]
102
- - Enforcement & Penalties (who enforces, how, consequences) [cite]
103
- - Deadlines & Effective Dates (explicit dates or 'not stated') [cite]
104
- - Exemptions/Thresholds (if any; otherwise 'not stated') [cite]
105
- - Risks & Open Questions (gaps/ambiguities; no speculation)
106
- - Action Checklist (practical steps derived strictly from the sources) [cite]
107
-
108
- # Comparative Table (render only if comparison triggers are present or {force_table}=True)
109
- - Render format: {render_format} (default markdown).
110
- - Columns (strict): Dimension | Document | Provision (Summary) | Deadlines/Dates | Enforcement/Penalties | Citation | Notes/Risks
111
- - Dimensions to use: {dimensions_hint_or_default}
112
- - Every non-trivial cell ends with a citation (filename p.X). Unknowns = '{unknown_token}'.
113
- - Sort by Dimension, then Document. If only one document exists, produce a single-column table.
114
 
115
  Topic hint: {topic_hint}
116
 
@@ -122,29 +61,11 @@ Context DOCS:
122
  "followup_reasoning": {
123
  "system": (
124
  "You are an assistant that explains policy documents interactively, reasoning step-by-step. "
125
- "Be strictly faithful to the documents; if a detail is absent, say so. "
126
- "Cite document filename and page for each factual claim. "
127
- f"{VERBOSITY_HINT} "
128
- "If comparison triggers are present, begin with a table-first comparative section as specified."
129
  ),
130
  "user_template": """User query: {query}
131
 
132
- # Comparative Table (render only if comparison triggers are present or {force_table}=True)
133
- - Render format: {render_format} (default markdown).
134
- - Columns (strict): Dimension | Document | Provision (Summary) | Deadlines/Dates | Enforcement/Penalties | Citation | Notes/Risks
135
- - Dimensions to use: {dimensions_hint_or_default}
136
- - Every non-trivial cell ends with a citation (filename p.X). Unknowns = '{unknown_token}'.
137
- - Sort by Dimension, then Document.
138
-
139
- Then answer step-by-step:
140
- 1) Direct Answer (what the sources actually support) with inline citations (filename p.X).
141
- 2) Why (short reasoning mapped to specific passages) with citations.
142
- 3) Edge Cases & Exceptions (only if present; otherwise 'not stated') with citations.
143
- 4) What’s Missing (explicitly note absent info; no speculation).
144
-
145
- Follow-up Q&A:
146
- - List 3–6 follow-up questions a reader might ask, and answer each using the docs.
147
- - If a follow-up cannot be answered with the docs, respond: 'Not found in sources.'
148
 
149
  Topic: {topic_hint}
150
 
@@ -153,42 +74,7 @@ DOCS:
153
  """
154
  },
155
 
156
- "comparison_matrix": {
157
- "system": (
158
- "You are a compliance-grade policy analyst. Zero hallucinations. "
159
- "Derive ONLY from the provided documents. If a detail is missing, write "
160
- f"'{COMPARISON_CONFIG['unknown_token']}'. No external knowledge. "
161
- "Produce a table-first answer with per-cell citations (filename p.X). "
162
- f"{VERBOSITY_HINT} "
163
- "If there is only one relevant document, state that and produce a single-column table; do not invent comparisons."
164
- ),
165
- "user_template": """Query: {query}
166
-
167
- Operating mode: Comparison/Matrix
168
-
169
- Output spec (in this order):
170
- 1) Comparison Table
171
- - Render format: {render_format} (default markdown).
172
- - Columns (strict): Dimension | Document | Provision (Summary) | Deadlines/Dates | Enforcement/Penalties | Citation | Notes/Risks
173
- - Dimensions to use: {dimensions_hint_or_default}
174
- - Populate rows for each (Dimension × Document) where evidence exists.
175
- - Every non-trivial cell ends with a citation (filename p.X). Unknowns = '{unknown_token}'.
176
- - Sort by Dimension, then Document.
177
-
178
- 2) Insights & Deltas (bulleted)
179
- - Top 3 material differences (what, where, why it matters) with citations.
180
- - Tightest requirement, earliest deadline, and heaviest penalty across docs (each with citations).
181
- - Ambiguities/Conflicts to watch (cite).
182
-
183
- 3) Risks & Unknowns
184
- - List critical gaps by Dimension. No speculation—flag as '{unknown_token}' with citations to show you checked.
185
-
186
- Topic hint: {topic_hint}
187
-
188
- Context DOCS:
189
- {context_block}
190
- """
191
- },
192
  }
193
 
194
 
@@ -222,6 +108,7 @@ def get_do_completion(api_key, model_name, messages, temperature=0.2, max_tokens
222
  return None
223
 
224
 
 
225
  # --- Prompt context builder ---
226
  def _clip(text: str, max_chars: int = 1400) -> str:
227
  """Trim content to limit prompt size."""
@@ -242,10 +129,10 @@ def build_context_block(top_docs: List[Dict[str, Any]]) -> str:
242
  for i, item in enumerate(top_docs):
243
  if hasattr(item, "page_content"):
244
  text = item.page_content
245
- meta = getattr(item, "metadata", {}) or {}
246
  else:
247
- text = item.get("text") or item.get("page_content", "") or ""
248
- meta = item.get("metadata", {}) or {}
249
 
250
  # Get file name from path
251
  full_path = meta.get("source", "")
@@ -255,99 +142,38 @@ def build_context_block(top_docs: List[Dict[str, Any]]) -> str:
255
  page_label = meta.get("page_label") or meta.get("page") or "unknown"
256
 
257
  citation = f"{filename}, p. {page_label}"
 
258
  blocks.append(f"<<<SOURCE: {citation}>>>\n{_clip(text)}\n</SOURCE>")
259
 
260
  return "\n".join(blocks)
261
 
262
 
263
- # --- Comparison trigger detection ---
264
- def has_comparison_trigger(*texts: Optional[str], extra_keywords: Optional[List[str]] = None) -> bool:
265
- """
266
- Returns True if any of the provided strings contain comparison triggers.
267
- """
268
- keys = set(COMPARISON_CONFIG["trigger_keywords"])
269
- if extra_keywords:
270
- keys.update(k.lower() for k in extra_keywords)
271
- pattern = re.compile(r"|".join(re.escape(k) for k in sorted(keys, key=len, reverse=True)), flags=re.IGNORECASE)
272
- for t in texts:
273
- if t and pattern.search(t):
274
- return True
275
- return False
276
-
277
-
278
- def _dimensions_hint_or_default(dimensions_hint: Optional[Union[str, List[str]]]) -> str:
279
- if isinstance(dimensions_hint, list):
280
- dims = [str(d).strip() for d in dimensions_hint if str(d).strip()]
281
- if dims:
282
- return ", ".join(dims)
283
- if isinstance(dimensions_hint, str) and dimensions_hint.strip():
284
- return dimensions_hint.strip()
285
- return ", ".join(COMPARISON_CONFIG["default_dimensions"])
286
-
287
-
288
  # --- Message builder ---
289
  def build_messages(
290
  query: str,
291
  top_docs: List[Dict[str, Any]],
292
  task_mode: str,
293
- sentiment_rollup: Optional[Dict[str, Any]] = None,
294
  coherence_report: str = "",
295
- topic_hint: str = "energy policy",
296
- force_table: bool = False,
297
- dimensions_hint: Optional[Union[str, List[str]]] = None,
298
- render_format: str = COMPARISON_CONFIG["default_render_format"],
299
- verbosity_hint: str = VERBOSITY_HINT,
300
- extra_comparison_keywords: Optional[List[str]] = None
301
  ) -> List[Dict[str, str]]:
302
- """
303
- Builds messages with conditional comparison mode and conditional sections.
304
- """
305
- # Auto-switch to comparison template if requested or triggered
306
- comparison_triggered = force_table or has_comparison_trigger(query, topic_hint, extra_keywords=extra_comparison_keywords)
307
- effective_task_mode = task_mode
308
- if task_mode == "auto" and comparison_triggered:
309
- effective_task_mode = "comparison_matrix"
310
-
311
- template = PROMPT_TEMPLATES.get(effective_task_mode)
312
  if not template:
313
- raise ValueError(f"Unknown task mode: {effective_task_mode}")
314
 
315
  context_block = build_context_block(top_docs)
316
  sentiment_json = json.dumps(sentiment_rollup or {}, ensure_ascii=False)
317
 
318
- # Prepare shared placeholders
319
- user_kwargs = {
320
- "query": query,
321
- "topic_hint": topic_hint,
322
- "sentiment_json": sentiment_json,
323
- "context_block": context_block,
324
- "coherence_report": coherence_report or "",
325
- "force_table": str(force_table),
326
- "render_format": (render_format or COMPARISON_CONFIG["default_render_format"]).lower(),
327
- "dimensions_hint_or_default": _dimensions_hint_or_default(dimensions_hint),
328
- "unknown_token": COMPARISON_CONFIG["unknown_token"],
329
- }
330
-
331
- user_prompt = template["user_template"].format(**user_kwargs)
332
-
333
- # If not using the dedicated comparison template but a table is triggered, ensure instructions are present.
334
- if comparison_triggered and effective_task_mode not in ("comparison_matrix",):
335
- # already included a “Comparative Table” section in templates above; nothing additional required
336
- pass
337
-
338
- system_prompt = template["system"]
339
- # Reinforce verbosity dynamically if caller passed a different hint
340
- if verbosity_hint and verbosity_hint not in system_prompt:
341
- system_prompt = system_prompt + " " + verbosity_hint
342
-
343
- # Conditional redaction: if sentiment/coherence are empty, strip their input blocks to avoid nudging the model
344
- if not sentiment_rollup:
345
- user_prompt = user_prompt.replace("Sentiment JSON (rolled-up across top docs):\n{sentiment_json}\n", "")
346
- if not coherence_report:
347
- user_prompt = user_prompt.replace("Coherence report:\n{coherence_report}\n", "")
348
 
349
  return [
350
- {"role": "system", "content": system_prompt},
351
  {"role": "user", "content": user_prompt}
352
  ]
353
 
@@ -358,20 +184,12 @@ def generate_policy_answer(
358
  model_name: str,
359
  query: str,
360
  top_docs: List[Union[Dict[str, Any], Any]],
361
- sentiment_rollup: Optional[Dict[str, Any]] = None,
362
  coherence_report: str = "",
363
- task_mode: str = "verbatim_sentiment", # 'verbatim_sentiment' | 'abstractive_summary' | 'followup_reasoning' | 'comparison_matrix' | 'auto'
364
  temperature: float = 0.2,
365
- max_tokens: int = 2000,
366
- force_table: bool = False,
367
- dimensions_hint: Optional[Union[str, List[str]]] = None,
368
- render_format: str = COMPARISON_CONFIG["default_render_format"],
369
- verbosity_hint: str = VERBOSITY_HINT,
370
- extra_comparison_keywords: Optional[List[str]] = None
371
  ) -> str:
372
- """
373
- Orchestrates the request with faithfulness guardrails and table-first comparison when indicated.
374
- """
375
  if not top_docs:
376
  return "No documents available to answer."
377
 
@@ -380,21 +198,14 @@ def generate_policy_answer(
380
  top_docs=top_docs,
381
  task_mode=task_mode,
382
  sentiment_rollup=sentiment_rollup,
383
- coherence_report=coherence_report,
384
- topic_hint="energy policy", # can be overridden by caller
385
- force_table=force_table,
386
- dimensions_hint=dimensions_hint,
387
- render_format=render_format,
388
- verbosity_hint=verbosity_hint,
389
- extra_comparison_keywords=extra_comparison_keywords
390
  )
391
-
392
  resp = get_do_completion(api_key, model_name, messages, temperature=temperature, max_tokens=max_tokens)
393
  if resp is None:
394
  return "Upstream model error. No response."
395
-
396
  try:
397
  return resp["choices"][0]["message"]["content"].strip()
398
  except Exception:
399
- # Fallback: return raw JSON so the caller can debug payload shape
400
  return json.dumps(resp, indent=2)
 
 
 
1
  import json
2
  import requests
3
+ from typing import List, Dict, Any, Union
4
  import time
5
  import numpy as np
6
+
7
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+
10
+
11
 
12
 
13
  PROMPT_TEMPLATES = {
14
  "verbatim_sentiment": {
15
  "system": (
16
+ "You are a compliance-grade policy analyst assistant. "
17
+ "Your job is to return a precise, fact-grounded response. "
18
+ "Avoid hallucinations. Base everything strictly on the content provided."
19
+ "if the coherence and or sentiment analysis is not enabled, do not mention it in the response."
 
 
 
 
 
 
 
20
  ),
21
  "user_template": """
22
  Query: {query}
23
 
24
+ Deliverables:
25
+ 1) **Quoted Policy Excerpts**: Quote key policy content directly. Cite the source using filename and page Do not leave out any information provided
26
+ 2) **Sentiment Summary**: Use the sentiment JSON to explain tone, gaps, penalties, or enforcement clarity in plain English.
27
+ 3) **Coherence Assessment**: Summarize the coherence report below. Highlight:
28
+ - Whether the answer was mostly on-topic or off-topic
29
+ - point out the sections that were coherent, off topic and repeated
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  Topic hint: {topic_hint}
32
 
 
44
  "abstractive_summary": {
45
  "system": (
46
  "You are a policy analyst summarizing government documents for a general audience. "
47
+ "Your response should paraphrase clearly, avoiding quotes unless absolutely necessary. "
48
+ "Highlight high-level goals, enforcement strategies, and important deadlines or penalties."
 
 
 
49
  ),
50
  "user_template": """Query: {query}
51
 
52
+ Summarize the answer in natural, non-technical language. Emphasize clarity and coverage. Avoid quoting unless the phrase is legally binding.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
  Topic hint: {topic_hint}
55
 
 
61
  "followup_reasoning": {
62
  "system": (
63
  "You are an assistant that explains policy documents interactively, reasoning step-by-step. "
64
+ "Always cite document IDs and indicate if certain info is absent."
 
 
 
65
  ),
66
  "user_template": """User query: {query}
67
 
68
+ Explain the answer step-by-step. Add follow-up questions that a reader might ask, and try to answer them using the documents below.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  Topic: {topic_hint}
71
 
 
74
  """
75
  },
76
 
77
+ # Add more templates as needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
 
80
 
 
108
  return None
109
 
110
 
111
+
112
  # --- Prompt context builder ---
113
  def _clip(text: str, max_chars: int = 1400) -> str:
114
  """Trim content to limit prompt size."""
 
129
  for i, item in enumerate(top_docs):
130
  if hasattr(item, "page_content"):
131
  text = item.page_content
132
+ meta = getattr(item, "metadata", {})
133
  else:
134
+ text = item.get("text") or item.get("page_content", "")
135
+ meta = item.get("metadata", {})
136
 
137
  # Get file name from path
138
  full_path = meta.get("source", "")
 
142
  page_label = meta.get("page_label") or meta.get("page") or "unknown"
143
 
144
  citation = f"{filename}, p. {page_label}"
145
+
146
  blocks.append(f"<<<SOURCE: {citation}>>>\n{_clip(text)}\n</SOURCE>")
147
 
148
  return "\n".join(blocks)
149
 
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  # --- Message builder ---
152
  def build_messages(
153
  query: str,
154
  top_docs: List[Dict[str, Any]],
155
  task_mode: str,
156
+ sentiment_rollup: Dict[str, List[str]],
157
  coherence_report: str = "",
158
+ topic_hint: str = "energy policy"
 
 
 
 
 
159
  ) -> List[Dict[str, str]]:
160
+ template = PROMPT_TEMPLATES.get(task_mode)
 
 
 
 
 
 
 
 
 
161
  if not template:
162
+ raise ValueError(f"Unknown task mode: {task_mode}")
163
 
164
  context_block = build_context_block(top_docs)
165
  sentiment_json = json.dumps(sentiment_rollup or {}, ensure_ascii=False)
166
 
167
+ user_prompt = template["user_template"].format(
168
+ query=query,
169
+ topic_hint=topic_hint,
170
+ sentiment_json=sentiment_json,
171
+ context_block=context_block,
172
+ coherence_report=coherence_report
173
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  return [
176
+ {"role": "system", "content": template["system"]},
177
  {"role": "user", "content": user_prompt}
178
  ]
179
 
 
184
  model_name: str,
185
  query: str,
186
  top_docs: List[Union[Dict[str, Any], Any]],
187
+ sentiment_rollup: Dict[str, List[str]],
188
  coherence_report: str = "",
189
+ task_mode: str = "verbatim_sentiment",
190
  temperature: float = 0.2,
191
+ max_tokens: int = 2000
 
 
 
 
 
192
  ) -> str:
 
 
 
193
  if not top_docs:
194
  return "No documents available to answer."
195
 
 
198
  top_docs=top_docs,
199
  task_mode=task_mode,
200
  sentiment_rollup=sentiment_rollup,
201
+ coherence_report=coherence_report
 
 
 
 
 
 
202
  )
 
203
  resp = get_do_completion(api_key, model_name, messages, temperature=temperature, max_tokens=max_tokens)
204
  if resp is None:
205
  return "Upstream model error. No response."
 
206
  try:
207
  return resp["choices"][0]["message"]["content"].strip()
208
  except Exception:
 
209
  return json.dumps(resp, indent=2)
210
+
211
+