NITISHRG15102007 commited on
Commit
64b4475
·
verified ·
1 Parent(s): a2071ee

Add grounded optimization modes and wire dedicated optimizer module

Browse files
Files changed (5) hide show
  1. README.md +16 -0
  2. app.py +25 -6
  3. env/prompt_optimizer.py +390 -0
  4. server/templates/ui.html +24 -5
  5. validate.py +3 -0
README.md CHANGED
@@ -178,6 +178,22 @@ curl http://localhost:7860/tasks
178
  curl http://localhost:7860/health
179
  ```
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  ## Baseline Scores
182
 
183
  The following scores were recorded from the current `inference.py` baseline against the local FastAPI server on April 7, 2026. By default, the baseline expects a real model-backed run. If you explicitly set `ALLOW_BASELINE_FALLBACK=1`, it can fall back to a deterministic heuristic policy for offline validation, and that mode is intended for local smoke testing rather than benchmark claims.
 
178
  curl http://localhost:7860/health
179
  ```
180
 
181
+ ### Prompt optimization modes
182
+
183
+ The `/optimize-prompt` helper supports three user-facing modes:
184
+
185
+ - `balanced`: default rewrite path that shortens prompts while keeping key constraints.
186
+ - `grounded`: prefers inline citations and stronger evidence anchoring, even if the final prompt is slightly less compressed.
187
+ - `aggressive`: prioritizes maximum shortening over richer evidence phrasing.
188
+
189
+ Example:
190
+
191
+ ```bash
192
+ curl -X POST http://localhost:7860/optimize-prompt \
193
+ -H "Content-Type: application/json" \
194
+ -d '{"prompt":"Draft a customer-safe incident update and cite evidence.","corpus_family":"enterprise_v2","compression_mode":"grounded"}'
195
+ ```
196
+
197
  ## Baseline Scores
198
 
199
  The following scores were recorded from the current `inference.py` baseline against the local FastAPI server on April 7, 2026. By default, the baseline expects a real model-backed run. If you explicitly set `ALLOW_BASELINE_FALLBACK=1`, it can fall back to a deterministic heuristic policy for offline validation, and that mode is intended for local smoke testing rather than benchmark claims.
app.py CHANGED
@@ -7,7 +7,6 @@ from __future__ import annotations
7
  from contextlib import asynccontextmanager
8
  from dataclasses import asdict, is_dataclass
9
  from pathlib import Path
10
- import re
11
  from typing import Any, Literal
12
 
13
  from fastapi import FastAPI, HTTPException, Request
@@ -15,9 +14,10 @@ from fastapi.middleware.cors import CORSMiddleware
15
  from fastapi.responses import HTMLResponse
16
  from pydantic import BaseModel
17
 
18
- from env.environment import RagContextOptimizerEnv
19
- from env.models import RagAction
20
  from env.corpus import list_corpus_families
 
21
  from env.tasks import ALL_TASKS, TASKS_BY_NAME
22
 
23
 
@@ -32,6 +32,7 @@ class ResetRequest(BaseModel):
32
  class OptimizePromptRequest(BaseModel):
33
  prompt: str
34
  corpus_family: str | None = None
 
35
 
36
 
37
  @asynccontextmanager
@@ -330,7 +331,21 @@ def _sentence_rank(query: str, text: str) -> list[str]:
330
  return [sentence for _score, sentence in ranked]
331
 
332
 
333
- async def _optimize_prompt_backend(prompt: str, corpus_family: str | None = None) -> dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  clean_prompt = prompt.strip()
335
  env = RagContextOptimizerEnv(
336
  task_name="single_domain_qa",
@@ -640,11 +655,15 @@ async def optimize_step_endpoint():
640
  return _suggest_action(env)
641
 
642
 
643
- @app.post("/optimize-prompt")
644
  async def optimize_prompt_endpoint(payload: OptimizePromptRequest):
645
  if not payload.prompt.strip():
646
  raise HTTPException(status_code=400, detail="Prompt must not be empty.")
647
- return await _optimize_prompt_backend(payload.prompt, corpus_family=payload.corpus_family)
 
 
 
 
648
 
649
 
650
  if __name__ == "__main__":
 
7
  from contextlib import asynccontextmanager
8
  from dataclasses import asdict, is_dataclass
9
  from pathlib import Path
 
10
  from typing import Any, Literal
11
 
12
  from fastapi import FastAPI, HTTPException, Request
 
14
  from fastapi.responses import HTMLResponse
15
  from pydantic import BaseModel
16
 
17
+ from env.environment import RagContextOptimizerEnv
18
+ from env.models import RagAction
19
  from env.corpus import list_corpus_families
20
+ from env.prompt_optimizer import CompressionMode, optimize_prompt
21
  from env.tasks import ALL_TASKS, TASKS_BY_NAME
22
 
23
 
 
32
  class OptimizePromptRequest(BaseModel):
33
  prompt: str
34
  corpus_family: str | None = None
35
+ compression_mode: CompressionMode = "balanced"
36
 
37
 
38
  @asynccontextmanager
 
331
  return [sentence for _score, sentence in ranked]
332
 
333
 
334
+ async def _optimize_prompt_backend(
335
+ prompt: str,
336
+ corpus_family: str | None = None,
337
+ compression_mode: CompressionMode = "balanced",
338
+ ) -> dict[str, Any]:
339
+ result = await optimize_prompt(prompt, corpus_family=corpus_family, mode=compression_mode)
340
+ return {
341
+ "optimized_prompt": result.optimized_prompt,
342
+ "stats": result.stats,
343
+ "grounding": result.grounding,
344
+ "context_tuning": result.context_tuning,
345
+ "corpus_family": result.corpus_family,
346
+ "selected_keywords": result.selected_keywords,
347
+ "optimization_mode": result.optimization_mode,
348
+ }
349
  clean_prompt = prompt.strip()
350
  env = RagContextOptimizerEnv(
351
  task_name="single_domain_qa",
 
655
  return _suggest_action(env)
656
 
657
 
658
+ @app.post("/optimize-prompt")
659
  async def optimize_prompt_endpoint(payload: OptimizePromptRequest):
660
  if not payload.prompt.strip():
661
  raise HTTPException(status_code=400, detail="Prompt must not be empty.")
662
+ return await _optimize_prompt_backend(
663
+ payload.prompt,
664
+ corpus_family=payload.corpus_family,
665
+ compression_mode=payload.compression_mode,
666
+ )
667
 
668
 
669
  if __name__ == "__main__":
env/prompt_optimizer.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Prompt optimization utilities for the HTTP app.
3
+
4
+ This module keeps prompt-rewriting and evidence-packaging logic out of `app.py`
5
+ so the FastAPI layer stays thinner and easier to review.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from dataclasses import dataclass
12
+ from typing import Any, Literal
13
+
14
+ from env.environment import RagContextOptimizerEnv
15
+
16
+
17
+ CompressionMode = Literal["balanced", "aggressive", "grounded"]
18
+
19
+ _PROMPT_STOPWORDS = {
20
+ "a", "an", "and", "are", "as", "at", "be", "but", "by", "can", "could", "do", "does", "did",
21
+ "for", "from", "had", "has", "have", "how", "i", "if", "in", "into", "is", "it", "its", "me",
22
+ "my", "of", "on", "or", "our", "should", "so", "than", "that", "the", "their", "them", "then",
23
+ "there", "these", "they", "this", "to", "too", "use", "using", "was", "we", "were", "what",
24
+ "when", "where", "which", "while", "with", "without", "would", "you", "your",
25
+ }
26
+
27
+ _INSTRUCTION_PRIORITY_TERMS = {
28
+ "must", "should", "only", "not", "never", "always", "include", "exclude", "cite", "answer",
29
+ "return", "draft", "write", "summarize", "compare", "explain", "verify", "preserve", "focus",
30
+ "keep", "avoid", "report", "escalate", "rollback", "refund", "incident", "customer", "security",
31
+ }
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class PromptOptimizationResult:
36
+ optimized_prompt: str
37
+ stats: dict[str, int]
38
+ grounding: dict[str, Any]
39
+ context_tuning: dict[str, Any]
40
+ corpus_family: str
41
+ selected_keywords: list[str]
42
+ optimization_mode: CompressionMode
43
+
44
+
45
+ def _tokenize(text: str) -> set[str]:
46
+ return set(re.findall(r"[a-z0-9]+", text.lower()))
47
+
48
+
49
+ def _content_terms(text: str) -> set[str]:
50
+ return {term for term in _tokenize(text) if len(term) > 2 and term not in _PROMPT_STOPWORDS}
51
+
52
+
53
+ def _clean_output_text(text: str) -> str:
54
+ cleaned = text.replace("```", " ").replace("---", " ")
55
+ cleaned = re.sub(r"\s+", " ", cleaned).strip()
56
+ cleaned = re.sub(r"[#*_`]+", "", cleaned)
57
+ cleaned = re.sub(r'\b(title|emoji|colorfrom|colorto|sdk|app_file|pinned)\s*:\s*', "", cleaned, flags=re.IGNORECASE)
58
+ return cleaned.strip(" -:")
59
+
60
+
61
+ def _compact_text(text: str, max_words: int = 28) -> str:
62
+ words = text.split()
63
+ if len(words) <= max_words:
64
+ return text
65
+ return " ".join(words[:max_words]).rstrip(" ,;:") + " ..."
66
+
67
+
68
+ def _approx_tokens(text: str) -> int:
69
+ return max(1, len(text.strip()) // 4) if text.strip() else 0
70
+
71
+
72
+ def _trim_sentence(sentence: str, max_terms: int) -> str:
73
+ words = re.findall(r"[A-Za-z0-9][A-Za-z0-9\-_\/]*|[,:;()]", sentence)
74
+ if not words:
75
+ return ""
76
+ kept: list[str] = []
77
+
78
+ for index, token in enumerate(words):
79
+ normalized = re.sub(r"[^A-Za-z0-9]+", "", token).lower()
80
+ if token in {",", ":", ";", "(", ")"}:
81
+ if kept and kept[-1] not in {",", ":", ";", "("}:
82
+ kept.append(token)
83
+ continue
84
+ is_priority = normalized in _INSTRUCTION_PRIORITY_TERMS
85
+ is_meaningful = (
86
+ normalized.isdigit()
87
+ or any(ch in token for ch in ("_", "-", "/"))
88
+ or len(normalized) >= 4
89
+ or is_priority
90
+ or index < 3
91
+ )
92
+ if not is_meaningful:
93
+ continue
94
+ if normalized in _PROMPT_STOPWORDS and not is_priority and index >= 3:
95
+ continue
96
+ kept.append(token)
97
+ if len([word for word in kept if word not in {",", ":", ";", "(", ")"}]) >= max_terms:
98
+ break
99
+
100
+ text = " ".join(kept)
101
+ text = re.sub(r"\s+([,:;)])", r"\1", text)
102
+ text = re.sub(r"(\()\s+", r"\1", text)
103
+ return text.strip(" ,;:")
104
+
105
+
106
+ def _rewrite_prompt_text(prompt: str, target_tokens: int) -> str:
107
+ raw = " ".join(prompt.strip().split())
108
+ if not raw:
109
+ return ""
110
+
111
+ sentences = [segment.strip() for segment in re.split(r"(?<=[.!?])\s+|\n+", raw) if segment.strip()]
112
+ if not sentences:
113
+ sentences = [raw]
114
+
115
+ rewritten: list[str] = []
116
+ used_terms = 0
117
+ max_terms = max(8, target_tokens)
118
+ for index, sentence in enumerate(sentences):
119
+ remaining = max_terms - used_terms
120
+ if remaining <= 0:
121
+ break
122
+ compact = _trim_sentence(sentence, max(4, remaining if index == 0 else min(remaining, 12)))
123
+ if not compact:
124
+ continue
125
+ rewritten.append(compact)
126
+ used_terms += len(compact.split())
127
+ if used_terms >= max_terms:
128
+ break
129
+
130
+ if not rewritten:
131
+ fallback = _trim_sentence(raw, max_terms)
132
+ return fallback or raw[: max(16, target_tokens * 4)].strip()
133
+
134
+ output = ". ".join(rewritten).strip()
135
+ if len(rewritten) == 1 and not output.endswith("."):
136
+ output += "."
137
+ return output
138
+
139
+
140
+ def _sentence_rank(query: str, text: str) -> list[str]:
141
+ query_terms = _tokenize(query)
142
+ sentences = [segment.strip() for segment in re.split(r"(?<=[.!?])\s+", text) if segment.strip()]
143
+ if not sentences:
144
+ return []
145
+
146
+ ranked: list[tuple[float, str]] = []
147
+ for index, sentence in enumerate(sentences):
148
+ sentence_terms = _tokenize(sentence)
149
+ overlap = len(query_terms & sentence_terms)
150
+ score = (overlap * 2.0) + (0.25 if index == 0 else 0.0)
151
+ ranked.append((score, sentence))
152
+
153
+ ranked.sort(key=lambda item: (-item[0], len(item[1])))
154
+ return [sentence for _score, sentence in ranked]
155
+
156
+
157
+ def _summarize_chunk_for_output(chunk: Any, effective_text: str) -> str:
158
+ if getattr(chunk, "domain", "").startswith("Project"):
159
+ keywords = ", ".join(chunk.keywords[:5])
160
+ domain = chunk.domain.replace("Project ", "").lower()
161
+ return _compact_text(f"This benchmark's {domain} covers {keywords}.", 24)
162
+ ranked_sentences = _sentence_rank(" ".join(chunk.keywords), _clean_output_text(effective_text))
163
+ if ranked_sentences:
164
+ return _compact_text(_clean_output_text(ranked_sentences[0]))
165
+ return _compact_text(_clean_output_text(effective_text))
166
+
167
+
168
+ def _target_ratio(input_tokens: int, mode: CompressionMode) -> float:
169
+ if mode == "aggressive":
170
+ if input_tokens <= 24:
171
+ return 0.78
172
+ if input_tokens <= 60:
173
+ return 0.66
174
+ if input_tokens <= 120:
175
+ return 0.58
176
+ return 0.52
177
+ if mode == "grounded":
178
+ if input_tokens <= 24:
179
+ return 0.98
180
+ if input_tokens <= 60:
181
+ return 0.90
182
+ if input_tokens <= 120:
183
+ return 0.84
184
+ return 0.78
185
+ if input_tokens <= 24:
186
+ return 0.85
187
+ if input_tokens <= 60:
188
+ return 0.75
189
+ if input_tokens <= 120:
190
+ return 0.68
191
+ return 0.62
192
+
193
+
194
+ def _fit_citations_into_prompt(
195
+ base_prompt: str,
196
+ citation_ids: list[str],
197
+ input_tokens: int,
198
+ target_tokens: int,
199
+ source_prompt: str,
200
+ mode: CompressionMode,
201
+ ) -> tuple[str, bool, str | None]:
202
+ if not citation_ids:
203
+ return base_prompt, False, "No high-confidence evidence anchors were selected."
204
+
205
+ prioritized = citation_ids[: (3 if mode == "grounded" else 2)]
206
+ suffix = " Evidence: " + " ".join(f"[{chunk_id}]" for chunk_id in prioritized)
207
+ with_all = (base_prompt.rstrip(".") + "." + suffix).strip()
208
+ if mode == "grounded" and _approx_tokens(with_all) <= max(input_tokens, target_tokens + 4):
209
+ return with_all, True, None
210
+ if _approx_tokens(with_all) < input_tokens:
211
+ return with_all, True, None
212
+
213
+ one_suffix = " Evidence: " + f"[{citation_ids[0]}]"
214
+ with_one = (base_prompt.rstrip(".") + "." + one_suffix).strip()
215
+ if mode == "grounded" and _approx_tokens(with_one) <= max(input_tokens, target_tokens + 2):
216
+ return with_one, True, None
217
+ if _approx_tokens(with_one) < input_tokens:
218
+ return with_one, True, None
219
+
220
+ tighter_target = max(8, target_tokens - (2 if mode == "grounded" else 3))
221
+ tighter_prompt = _rewrite_prompt_text(source_prompt, tighter_target)
222
+ tighter_with_one = (tighter_prompt.rstrip(".") + "." + one_suffix).strip()
223
+ if mode == "grounded" and _approx_tokens(tighter_with_one) <= max(input_tokens, target_tokens + 2):
224
+ return tighter_with_one, True, None
225
+ if _approx_tokens(tighter_with_one) < input_tokens:
226
+ return tighter_with_one, True, None
227
+
228
+ if mode == "grounded":
229
+ forced = (tighter_prompt.rstrip(".") + "." + one_suffix).strip()
230
+ return forced, True, "Grounded mode preserved at least one inline citation, even at the cost of a slightly longer prompt."
231
+
232
+ return base_prompt, False, "Citations were omitted to keep the optimized prompt shorter than the original. Use grounded mode or the evidence notes below if explicit anchors are required."
233
+
234
+
235
+ async def optimize_prompt(
236
+ prompt: str,
237
+ corpus_family: str | None = None,
238
+ mode: CompressionMode = "balanced",
239
+ ) -> PromptOptimizationResult:
240
+ clean_prompt = prompt.strip()
241
+ env = RagContextOptimizerEnv(
242
+ task_name="single_domain_qa",
243
+ query_override=clean_prompt,
244
+ token_budget_override=800,
245
+ max_steps_override=6,
246
+ corpus_family_override=corpus_family,
247
+ )
248
+ await env.reset()
249
+
250
+ tuning = env._last_tuning or env.context_tuner.tune(clean_prompt, env._available_chunks)
251
+
252
+ ranked_candidates = []
253
+ for chunk in env._available_chunks:
254
+ tuned = tuning.tuned_scores.get(chunk.chunk_id)
255
+ score = tuned.final_score if tuned is not None else env.retriever.hybrid_score(clean_prompt, chunk)
256
+ if score < 0.16:
257
+ continue
258
+ ranked_candidates.append((chunk, score, tuned))
259
+ ranked_candidates.sort(
260
+ key=lambda item: (
261
+ -(item[2].citation_prior if item[2] is not None else 0.0) if mode == "grounded" else 0.0,
262
+ -(item[1] / max(item[0].tokens, 1)),
263
+ -item[1],
264
+ item[0].chunk_id,
265
+ )
266
+ )
267
+
268
+ selected_ids: list[str] = []
269
+ token_cap = 420 if mode == "grounded" else 360
270
+ running_tokens = 0
271
+ for chunk, score, _tuned in ranked_candidates:
272
+ if len(selected_ids) >= (5 if mode == "grounded" else 4):
273
+ break
274
+ if score < (0.18 if mode == "grounded" else 0.22) and selected_ids:
275
+ break
276
+ projected = running_tokens + chunk.tokens
277
+ if projected > token_cap and selected_ids:
278
+ continue
279
+ selected_ids.append(chunk.chunk_id)
280
+ env._selected_chunks.append(chunk.chunk_id)
281
+ running_tokens += chunk.tokens
282
+
283
+ if not selected_ids and ranked_candidates:
284
+ best_chunk = ranked_candidates[0][0]
285
+ selected_ids.append(best_chunk.chunk_id)
286
+ env._selected_chunks.append(best_chunk.chunk_id)
287
+
288
+ for chunk_id in list(selected_ids):
289
+ chunk = env._chunk_map().get(chunk_id)
290
+ if chunk is None:
291
+ continue
292
+ tuned = tuning.tuned_scores.get(chunk_id)
293
+ score = tuned.final_score if tuned is not None else env.retriever.hybrid_score(clean_prompt, chunk)
294
+ ratio = tuned.compression_ratio if tuned is not None else 0.5
295
+ if mode == "grounded":
296
+ ratio = max(ratio, 0.68 if score >= 0.55 else 0.58)
297
+ elif score >= 0.75:
298
+ ratio = max(ratio, 0.6)
299
+ env._compression_ratios[chunk_id] = ratio
300
+
301
+ input_tokens = _approx_tokens(clean_prompt)
302
+ target_tokens = max(12, int(input_tokens * _target_ratio(input_tokens, mode)))
303
+ target_tokens = min(target_tokens, 120 if mode == "grounded" else 80)
304
+
305
+ rewritten = _rewrite_prompt_text(clean_prompt, target_tokens=target_tokens)
306
+
307
+ distilled_points: list[tuple[str, str]] = []
308
+ for chunk_id in env._selected_chunks:
309
+ chunk = env._chunk_map().get(chunk_id)
310
+ if chunk is None:
311
+ continue
312
+ best = _summarize_chunk_for_output(chunk, env._effective_chunk_text(chunk_id))
313
+ if best and all(existing != best for _cid, existing in distilled_points):
314
+ distilled_points.append((chunk_id, best))
315
+ if len(distilled_points) >= (3 if mode == "grounded" else (2 if input_tokens < 80 else 3)):
316
+ break
317
+
318
+ lines: list[str] = [rewritten if rewritten else clean_prompt]
319
+ if distilled_points and (mode == "grounded" or input_tokens >= 80):
320
+ lines.append("")
321
+ lines.append("Context:")
322
+ lines.extend([f"- [{chunk_id}] {point}" for chunk_id, point in distilled_points])
323
+
324
+ optimized_prompt = "\n".join(lines).strip()
325
+
326
+ if mode != "grounded" and input_tokens > 0 and _approx_tokens(optimized_prompt) >= input_tokens:
327
+ max_chars = max(12, (input_tokens - 1) * 4)
328
+ optimized_prompt = optimized_prompt[:max_chars].rstrip(" ,;:\n\t")
329
+ if optimized_prompt and not optimized_prompt.endswith("..."):
330
+ optimized_prompt = optimized_prompt + " ..."
331
+ while input_tokens > 1 and _approx_tokens(optimized_prompt) >= input_tokens and len(optimized_prompt) > 12:
332
+ optimized_prompt = optimized_prompt[:-6].rstrip(" ,;:\n\t") + " ..."
333
+ if input_tokens > 1 and _approx_tokens(optimized_prompt) >= input_tokens:
334
+ optimized_prompt = _rewrite_prompt_text(clean_prompt, target_tokens=max(5, input_tokens - 1))
335
+ if optimized_prompt and not optimized_prompt.endswith("...") and _approx_tokens(optimized_prompt) >= input_tokens:
336
+ optimized_prompt = optimized_prompt[: max(8, (input_tokens - 1) * 4)].strip() + " ..."
337
+
338
+ optimized_prompt, citation_ready, citation_guidance = _fit_citations_into_prompt(
339
+ optimized_prompt,
340
+ tuning.suggested_citations or list(env._selected_chunks),
341
+ input_tokens,
342
+ target_tokens,
343
+ clean_prompt,
344
+ mode,
345
+ )
346
+
347
+ original_prompt_tokens = input_tokens
348
+ optimized_prompt_tokens = _approx_tokens(optimized_prompt)
349
+ source_tokens = sum(env._chunk_map()[chunk_id].tokens for chunk_id in env._selected_chunks if chunk_id in env._chunk_map())
350
+ compressed_tokens = sum(env._effective_chunk_tokens(chunk_id) for chunk_id in env._selected_chunks)
351
+ evidence_terms = _content_terms(" ".join(env._effective_chunk_text(chunk_id) for chunk_id in env._selected_chunks))
352
+ prompt_terms = _content_terms(optimized_prompt)
353
+ inline_citations = set(re.findall(r"\[([a-z0-9_]+)\]", optimized_prompt.lower()))
354
+ grounded_overlap = (len(prompt_terms & evidence_terms) / len(prompt_terms)) if prompt_terms else 0.0
355
+
356
+ return PromptOptimizationResult(
357
+ optimized_prompt=optimized_prompt,
358
+ stats={
359
+ "selected_chunks": len(env._selected_chunks),
360
+ "source_tokens": source_tokens,
361
+ "compressed_context_tokens": compressed_tokens,
362
+ "original_prompt_tokens": original_prompt_tokens,
363
+ "optimized_prompt_tokens": optimized_prompt_tokens,
364
+ "compression_gain": max(0, source_tokens - compressed_tokens),
365
+ },
366
+ grounding={
367
+ "citations": tuning.suggested_citations or list(env._selected_chunks),
368
+ "citation_ready": citation_ready and bool(inline_citations),
369
+ "citation_guidance": citation_guidance,
370
+ "grounded_overlap": round(grounded_overlap, 3),
371
+ "evidence_notes": [
372
+ {"chunk_id": chunk_id, "note": note}
373
+ for chunk_id, note in distilled_points
374
+ ],
375
+ },
376
+ context_tuning={
377
+ "mode": tuning.mode,
378
+ "top_demo_cases": tuning.top_demo_cases,
379
+ "suggested_citations": tuning.suggested_citations,
380
+ "token_dropout": tuning.token_dropout,
381
+ "leave_one_out": tuning.leave_one_out,
382
+ },
383
+ corpus_family=env._corpus_family,
384
+ selected_keywords=[
385
+ keyword
386
+ for chunk_id in env._selected_chunks
387
+ for keyword in (env._chunk_map().get(chunk_id).keywords if env._chunk_map().get(chunk_id) else [])
388
+ ][:10],
389
+ optimization_mode=mode,
390
+ )
server/templates/ui.html CHANGED
@@ -326,6 +326,12 @@
326
  <option value="enterprise_v1">enterprise_v1</option>
327
  <option value="enterprise_v2">enterprise_v2</option>
328
  </select>
 
 
 
 
 
 
329
  <button id="optimizeBtn" class="primary" type="button">Optimize Prompt</button>
330
  <button id="copyBtn" class="primary" type="button">Copy Output</button>
331
  <span id="busyLabel" class="mini">Idle</span>
@@ -342,7 +348,7 @@
342
  <div class="stat"><div class="stat-label">Demo Seeds</div><div class="stat-value" id="demoSeedStat">-</div></div>
343
  </div>
344
  <div id="statusBox" class="status">Ready. Paste a prompt and click Optimize Prompt.</div>
345
- <p class="mini" id="citationGuidance">Citation guidance will appear here when relevant.</p>
346
  <div class="chips" id="keywordChips"></div>
347
  <div class="chips" id="citationChips"></div>
348
  <div class="chips" id="demoChips"></div>
@@ -379,6 +385,7 @@
379
  optimizeBtn: document.getElementById("optimizeBtn"),
380
  copyBtn: document.getElementById("copyBtn"),
381
  corpusFamilySelect: document.getElementById("corpusFamilySelect"),
 
382
  busyLabel: document.getElementById("busyLabel"),
383
  statusBox: document.getElementById("statusBox"),
384
  citationGuidance: document.getElementById("citationGuidance"),
@@ -437,18 +444,26 @@
437
  els.finalTokensStat.textContent = (stats.optimized_prompt_tokens !== undefined && stats.optimized_prompt_tokens !== null) ? String(stats.optimized_prompt_tokens) : "-";
438
  const grounding = result.grounding || {};
439
  const tuning = result.context_tuning || {};
 
440
  els.citationReadyStat.textContent = grounding.citation_ready ? "Yes" : "No";
441
  els.groundedOverlapStat.textContent = (grounding.grounded_overlap !== undefined && grounding.grounded_overlap !== null) ? String(grounding.grounded_overlap) : "-";
442
  els.tuningModeStat.textContent = tuning.mode || "-";
443
  els.demoSeedStat.textContent = tuning.top_demo_cases ? String(tuning.top_demo_cases.length) : "-";
444
  const sourceTokens = (stats.source_tokens !== undefined && stats.source_tokens !== null) ? String(stats.source_tokens) : "-";
445
- els.summaryNote.textContent = "`Current Input` tracks the text in the prompt box. `Context Tokens` is compressed backend support context. Raw support before compression: " + sourceTokens + ". The optimized output now uses context tuning seeded from task demonstrations, then grounded with evidence anchors.";
446
  if (result.corpus_family) {
447
  els.summaryNote.textContent += " Active corpus pack: " + result.corpus_family + ".";
448
  }
449
  if (tuning.leave_one_out) {
450
  els.summaryNote.textContent += " Leave-one-out masking is active.";
451
  }
 
 
 
 
 
 
 
452
  const chips = result.selected_keywords || [];
453
  els.keywordChips.innerHTML = chips.map((keyword) => `<span class="chip">${keyword}</span>`).join("");
454
  const citations = grounding.citations || [];
@@ -471,9 +486,13 @@
471
  try {
472
  const result = await fetchJson("/optimize-prompt", {
473
  method: "POST",
474
- body: JSON.stringify({ prompt, corpus_family: els.corpusFamilySelect.value }),
 
 
 
 
475
  });
476
- renderResult(result);
477
  setStatus("Optimization complete. The output below is ready to copy and paste.", "good");
478
  } catch (error) {
479
  setStatus(error.message, "error");
@@ -505,7 +524,7 @@
505
  els.groundedOverlapStat.textContent = "-";
506
  els.tuningModeStat.textContent = "-";
507
  els.demoSeedStat.textContent = "-";
508
- els.citationGuidance.textContent = "Citation guidance will appear here when relevant.";
509
  els.citationChips.innerHTML = "";
510
  els.demoChips.innerHTML = "";
511
  els.groundingList.innerHTML = "";
 
326
  <option value="enterprise_v1">enterprise_v1</option>
327
  <option value="enterprise_v2">enterprise_v2</option>
328
  </select>
329
+ <label class="mini" for="compressionModeSelect">Mode</label>
330
+ <select id="compressionModeSelect">
331
+ <option value="balanced">balanced</option>
332
+ <option value="grounded">grounded</option>
333
+ <option value="aggressive">aggressive</option>
334
+ </select>
335
  <button id="optimizeBtn" class="primary" type="button">Optimize Prompt</button>
336
  <button id="copyBtn" class="primary" type="button">Copy Output</button>
337
  <span id="busyLabel" class="mini">Idle</span>
 
348
  <div class="stat"><div class="stat-label">Demo Seeds</div><div class="stat-value" id="demoSeedStat">-</div></div>
349
  </div>
350
  <div id="statusBox" class="status">Ready. Paste a prompt and click Optimize Prompt.</div>
351
+ <p class="mini" id="citationGuidance">Citation guidance will appear here. Use grounded mode when you want the optimizer to prefer inline evidence anchors.</p>
352
  <div class="chips" id="keywordChips"></div>
353
  <div class="chips" id="citationChips"></div>
354
  <div class="chips" id="demoChips"></div>
 
385
  optimizeBtn: document.getElementById("optimizeBtn"),
386
  copyBtn: document.getElementById("copyBtn"),
387
  corpusFamilySelect: document.getElementById("corpusFamilySelect"),
388
+ compressionModeSelect: document.getElementById("compressionModeSelect"),
389
  busyLabel: document.getElementById("busyLabel"),
390
  statusBox: document.getElementById("statusBox"),
391
  citationGuidance: document.getElementById("citationGuidance"),
 
444
  els.finalTokensStat.textContent = (stats.optimized_prompt_tokens !== undefined && stats.optimized_prompt_tokens !== null) ? String(stats.optimized_prompt_tokens) : "-";
445
  const grounding = result.grounding || {};
446
  const tuning = result.context_tuning || {};
447
+ const optimizationMode = result.optimization_mode || els.compressionModeSelect.value || "balanced";
448
  els.citationReadyStat.textContent = grounding.citation_ready ? "Yes" : "No";
449
  els.groundedOverlapStat.textContent = (grounding.grounded_overlap !== undefined && grounding.grounded_overlap !== null) ? String(grounding.grounded_overlap) : "-";
450
  els.tuningModeStat.textContent = tuning.mode || "-";
451
  els.demoSeedStat.textContent = tuning.top_demo_cases ? String(tuning.top_demo_cases.length) : "-";
452
  const sourceTokens = (stats.source_tokens !== undefined && stats.source_tokens !== null) ? String(stats.source_tokens) : "-";
453
+ els.summaryNote.textContent = "`Current Input` tracks the text in the prompt box. `Context Tokens` is compressed backend support context. Raw support before compression: " + sourceTokens + ". Active optimization mode: " + optimizationMode + ".";
454
  if (result.corpus_family) {
455
  els.summaryNote.textContent += " Active corpus pack: " + result.corpus_family + ".";
456
  }
457
  if (tuning.leave_one_out) {
458
  els.summaryNote.textContent += " Leave-one-out masking is active.";
459
  }
460
+ if (optimizationMode === "grounded") {
461
+ els.summaryNote.textContent += " Grounded mode prefers inline citations even if the final prompt stays slightly longer.";
462
+ } else if (optimizationMode === "aggressive") {
463
+ els.summaryNote.textContent += " Aggressive mode prioritizes shorter output over richer evidence phrasing.";
464
+ } else {
465
+ els.summaryNote.textContent += " Balanced mode tries to preserve constraints while still shortening the prompt.";
466
+ }
467
  const chips = result.selected_keywords || [];
468
  els.keywordChips.innerHTML = chips.map((keyword) => `<span class="chip">${keyword}</span>`).join("");
469
  const citations = grounding.citations || [];
 
486
  try {
487
  const result = await fetchJson("/optimize-prompt", {
488
  method: "POST",
489
+ body: JSON.stringify({
490
+ prompt,
491
+ corpus_family: els.corpusFamilySelect.value,
492
+ compression_mode: els.compressionModeSelect.value,
493
+ }),
494
  });
495
+ renderResult(result);
496
  setStatus("Optimization complete. The output below is ready to copy and paste.", "good");
497
  } catch (error) {
498
  setStatus(error.message, "error");
 
524
  els.groundedOverlapStat.textContent = "-";
525
  els.tuningModeStat.textContent = "-";
526
  els.demoSeedStat.textContent = "-";
527
+ els.citationGuidance.textContent = "Citation guidance will appear here. Use grounded mode when you want the optimizer to prefer inline evidence anchors.";
528
  els.citationChips.innerHTML = "";
529
  els.demoChips.innerHTML = "";
530
  els.groundingList.innerHTML = "";
validate.py CHANGED
@@ -185,6 +185,7 @@ def main() -> int:
185
  json={
186
  "prompt": "Draft a customer-safe admin compromise update with rollback safeguards and cite evidence.",
187
  "corpus_family": "enterprise_v2",
 
188
  },
189
  )
190
  optimize_body = optimize_prompt.json() if optimize_prompt.status_code == 200 else {}
@@ -193,6 +194,8 @@ def main() -> int:
193
  and "optimized_prompt" in optimize_body
194
  and "context_tuning" in optimize_body
195
  and "grounding" in optimize_body
 
 
196
  )
197
  print_check("POST /optimize-prompt", optimize_ok)
198
  all_passed &= optimize_ok
 
185
  json={
186
  "prompt": "Draft a customer-safe admin compromise update with rollback safeguards and cite evidence.",
187
  "corpus_family": "enterprise_v2",
188
+ "compression_mode": "grounded",
189
  },
190
  )
191
  optimize_body = optimize_prompt.json() if optimize_prompt.status_code == 200 else {}
 
194
  and "optimized_prompt" in optimize_body
195
  and "context_tuning" in optimize_body
196
  and "grounding" in optimize_body
197
+ and optimize_body.get("optimization_mode") == "grounded"
198
+ and bool(optimize_body.get("grounding", {}).get("citation_ready"))
199
  )
200
  print_check("POST /optimize-prompt", optimize_ok)
201
  all_passed &= optimize_ok