Spaces:
Sleeping
Sleeping
Cyber Catalyst Team commited on
Commit ·
cbc1879
1
Parent(s): b708c13
feat: implement bulletproof history compaction and Set Recommended Model settings endpoint
Browse files- backend.py +58 -37
backend.py
CHANGED
|
@@ -657,53 +657,60 @@ IMPORTANT RULES:
|
|
| 657 |
|
| 658 |
def compact_history(messages: list) -> list:
|
| 659 |
"""
|
| 660 |
-
|
| 661 |
-
|
| 662 |
"""
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
compacted.append(messages[0])
|
| 671 |
start_idx = 1
|
| 672 |
else:
|
| 673 |
start_idx = 0
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
recent_count = 4
|
| 677 |
-
mid_messages = messages[start_idx:-recent_count]
|
| 678 |
-
recent_messages = messages[-recent_count:]
|
| 679 |
|
| 680 |
-
|
|
|
|
|
|
|
|
|
|
| 681 |
role = msg.get("role")
|
| 682 |
content = msg.get("content") or ""
|
|
|
|
| 683 |
|
| 684 |
-
if
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
pass
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
|
|
|
|
|
|
|
|
|
| 703 |
|
| 704 |
-
|
| 705 |
-
log_activity(f"[Auto-Compaction] Compressed message history from {len(messages)} down to {len(compacted)}")
|
| 706 |
-
return compacted
|
| 707 |
|
| 708 |
|
| 709 |
@app.post("/v1/chat/completions")
|
|
@@ -2457,6 +2464,20 @@ async def download_backup(authorization: str = Header(None)):
|
|
| 2457 |
raise HTTPException(status_code=500, detail=str(e))
|
| 2458 |
|
| 2459 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2460 |
@app.get("/health")
|
| 2461 |
async def health():
|
| 2462 |
return {
|
|
|
|
| 657 |
|
| 658 |
def compact_history(messages: list) -> list:
|
| 659 |
"""
|
| 660 |
+
Bulletproof conversation history compaction.
|
| 661 |
+
Guarantees that the total context length stays well below the model's token limits.
|
| 662 |
"""
|
| 663 |
+
if not messages:
|
| 664 |
+
return []
|
| 665 |
+
|
| 666 |
+
# Find and preserve the system prompt
|
| 667 |
+
system_msg = None
|
| 668 |
+
if messages[0].get("role") == "system":
|
| 669 |
+
system_msg = messages[0]
|
|
|
|
| 670 |
start_idx = 1
|
| 671 |
else:
|
| 672 |
start_idx = 0
|
| 673 |
+
|
| 674 |
+
other_messages = messages[start_idx:]
|
|
|
|
|
|
|
|
|
|
| 675 |
|
| 676 |
+
# 1. Truncate individually massive messages (e.g. file contents or massive bash outputs)
|
| 677 |
+
# Even recent messages should be compacted if they are ridiculously large!
|
| 678 |
+
compacted_others = []
|
| 679 |
+
for msg in other_messages:
|
| 680 |
role = msg.get("role")
|
| 681 |
content = msg.get("content") or ""
|
| 682 |
+
msg_copy = dict(msg)
|
| 683 |
|
| 684 |
+
if len(content) > 15000:
|
| 685 |
+
msg_copy["content"] = content[:5000] + f"\n\n[... Truncated {len(content) - 10000} characters to prevent model context limits from overflowing ...]\n\n" + content[-5000:]
|
| 686 |
+
|
| 687 |
+
compacted_others.append(msg_copy)
|
| 688 |
+
|
| 689 |
+
# 2. Enforce total character size budget (max ~300,000 characters / ~75,000 tokens)
|
| 690 |
+
# Iterate backwards from newest to oldest
|
| 691 |
+
final_list = []
|
| 692 |
+
total_chars = 0
|
| 693 |
+
max_budget = 300000
|
| 694 |
+
|
| 695 |
+
for msg in reversed(compacted_others):
|
| 696 |
+
msg_len = len(msg.get("content") or "")
|
| 697 |
+
if total_chars + msg_len < max_budget or len(final_list) < 2:
|
| 698 |
+
final_list.append(msg)
|
| 699 |
+
total_chars += msg_len
|
| 700 |
+
else:
|
| 701 |
+
# Drop older messages once we exceed context window budget
|
| 702 |
pass
|
| 703 |
+
|
| 704 |
+
# Reverse back to chronological order
|
| 705 |
+
final_list.reverse()
|
| 706 |
+
|
| 707 |
+
if system_msg:
|
| 708 |
+
final_list.insert(0, system_msg)
|
| 709 |
+
|
| 710 |
+
if len(messages) != len(final_list):
|
| 711 |
+
log_activity(f"[Auto-Compaction] Sliced context window from {len(messages)} down to {len(final_list)} messages (Total chars: {total_chars})")
|
| 712 |
|
| 713 |
+
return final_list
|
|
|
|
|
|
|
| 714 |
|
| 715 |
|
| 716 |
@app.post("/v1/chat/completions")
|
|
|
|
| 2464 |
raise HTTPException(status_code=500, detail=str(e))
|
| 2465 |
|
| 2466 |
|
| 2467 |
+
class SetModelRequest(BaseModel):
|
| 2468 |
+
model: str
|
| 2469 |
+
|
| 2470 |
+
@app.post("/api/settings/model")
|
| 2471 |
+
async def set_recommended_model(req: SetModelRequest, authorization: str = Header(None)):
|
| 2472 |
+
auth(authorization)
|
| 2473 |
+
global RECOMMENDED_MODEL
|
| 2474 |
+
if req.model not in TOOL_CAPABLE_MODELS:
|
| 2475 |
+
raise HTTPException(status_code=400, detail="Invalid or unsupported agent model")
|
| 2476 |
+
RECOMMENDED_MODEL = req.model
|
| 2477 |
+
log_activity(f"[Settings] Recommended model manually updated to: {RECOMMENDED_MODEL}")
|
| 2478 |
+
return {"status": "ok", "recommended_model": RECOMMENDED_MODEL}
|
| 2479 |
+
|
| 2480 |
+
|
| 2481 |
@app.get("/health")
|
| 2482 |
async def health():
|
| 2483 |
return {
|