Add grounded optimization modes and wire dedicated optimizer module
Browse files- README.md +16 -0
- app.py +25 -6
- env/prompt_optimizer.py +390 -0
- server/templates/ui.html +24 -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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 + ".
|
| 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({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|