Ensure reasoning effort is always explicitly displayed with model name

This commit is contained in:
Antigravity Bot
2026-08-30 13:42:41 +03:30
parent 9ba4ba4fdb
commit 4e4b3e6859
5 changed files with 198 additions and 63 deletions
+138 -5
View File
@@ -115,6 +115,7 @@ class Project:
last_response: Optional[str] = None
created_at: float = field(default_factory=time.time)
description: str = ""
conversation_titles: Dict[str, str] = field(default_factory=dict)
@dataclass
class Session:
@@ -249,6 +250,7 @@ class SessionManager:
"last_response",
"created_at",
"description",
"conversation_titles",
)
}
if "name" not in clean_p:
@@ -261,6 +263,8 @@ class SessionManager:
clean_p["conversation_history"] = []
if clean_p.get("conversation_id") and clean_p["conversation_id"] not in clean_p["conversation_history"]:
clean_p["conversation_history"].append(clean_p["conversation_id"])
if "conversation_titles" not in clean_p or not isinstance(clean_p["conversation_titles"], dict):
clean_p["conversation_titles"] = {}
# Non-admin users cannot own or access the "default" /root project
if not is_admin_user and (p_name == "default" or clean_p.get("workspace") == settings.default_workspace):
@@ -334,6 +338,7 @@ class SessionManager:
"last_response": p_obj.last_response,
"created_at": p_obj.created_at,
"description": p_obj.description,
"conversation_titles": p_obj.conversation_titles,
}
curr = self.get_current_project(sess.chat_id)
data[str(k)] = {
@@ -950,6 +955,9 @@ class SessionManager:
except Exception as e:
logger.error(f"Error removing brain dir {brain_path}: {e}")
if hasattr(curr, "conversation_titles") and matched_cid in curr.conversation_titles:
curr.conversation_titles.pop(matched_cid, None)
self.save()
return True, f"✅ گفتگوی <code>{matched_cid[:8]}...</code> با موفقیت حذف شد."
@@ -981,6 +989,8 @@ class SessionManager:
curr.conversation_history = []
curr.conversation_id = None
if hasattr(curr, "conversation_titles"):
curr.conversation_titles = {}
curr.last_response = None
curr.last_context_length = None
curr.last_total_tokens = None
@@ -990,6 +1000,55 @@ class SessionManager:
self.save()
return True, f"✅ تمام {count} گفتگوی پروژه با موفقیت حذف و پاکسازی شدند.", count
def set_conversation_title(self, chat_id: int, title: str, conv_id: Optional[str] = None, project_name: Optional[str] = None) -> tuple[bool, str, str]:
"""
Sets a custom title / topic for the specified conversation (or active conversation).
Returns (success, message, matched_conv_id).
"""
session = self.get_or_create(chat_id)
target_proj = None
if project_name:
accessible = self.get_all_accessible_projects(chat_id)
for k, p in accessible.items():
if k.lower() == project_name.lower() or p.name.lower() == project_name.lower():
target_proj = p
break
if not target_proj:
target_proj = self.get_current_project(chat_id)
if not target_proj:
return False, "⚠️ پروژه‌ای یافت نشد.", ""
clean_title = (title or "").strip()
if not clean_title:
return False, "⚠️ عنوان مشخص نشده است.", ""
target_cid = conv_id.strip() if conv_id else (target_proj.conversation_id or "")
# If user passed a number like "1", "#1", or partial ID
if target_cid and (target_cid.startswith("#") or target_cid.isdigit() or len(target_cid) < 15):
convs = self.get_project_conversations(chat_id)
clean_num = target_cid.lstrip("#")
if clean_num.isdigit():
idx = int(clean_num)
if 1 <= idx <= len(convs):
target_cid = convs[idx - 1]["id"]
else:
for c in convs:
if c["id"].lower().startswith(target_cid.lower()):
target_cid = c["id"]
break
if not target_cid:
return False, "⚠️ گفتگوی فعالی برای تغییر عنوان یافت نشد.", ""
if not hasattr(target_proj, "conversation_titles") or not isinstance(target_proj.conversation_titles, dict):
target_proj.conversation_titles = {}
target_proj.conversation_titles[target_cid] = clean_title
self.save()
return True, f"✅ عنوان گفتگو به «<b>{escape_html(clean_title)}</b>» تغییر یافت.", target_cid
def get_project_conversations(self, chat_id: int) -> List[Dict[str, Any]]:
"""
Returns sorted list of conversation summaries for active project (newest first).
@@ -1017,14 +1076,17 @@ class SessionManager:
logger.debug(f"Error scanning brain dir: {e}")
result = []
titles_map = getattr(curr, "conversation_titles", {}) or {}
for cid in conv_ids:
meta = get_conversation_metadata(cid)
if not meta.get("file_exists") and cid not in curr.conversation_history:
continue
is_current = (cid == curr.conversation_id)
custom_title = titles_map.get(cid)
result.append({
"id": cid,
"is_current": is_current,
"title": custom_title,
"first_prompt": meta.get("first_prompt") or "(بدون متن)",
"last_prompt": meta.get("last_prompt") or "",
"turns_count": meta.get("turns_count", 0),
@@ -1037,6 +1099,7 @@ class SessionManager:
result.sort(key=lambda x: x.get("last_updated") or 0.0, reverse=True)
return result
async def set_model(self, chat_id: int, model: str):
session = self.get_or_create(chat_id)
curr = self.get_current_project(chat_id)
@@ -1110,17 +1173,61 @@ def strip_ansi(text: str) -> str:
return ANSI_REGEX.sub("", text)
def clean_user_prompt(raw_text: Optional[str]) -> str:
"""
Cleans raw prompt or transcript user input, stripping all internal system instructions,
prompts metadata, memory blocks, and file markers to extract the actual user message.
"""
if not raw_text:
return ""
text = raw_text
text = str(raw_text)
# 1. Extract USER_REQUEST content if present
req_match = re.search(r"<USER_REQUEST>(.*?)</USER_REQUEST>", text, re.DOTALL)
if req_match:
text = req_match.group(1)
text = re.sub(r"\[SYSTEM INSTRUCTION:.*?\]", "", text, flags=re.DOTALL)
# 2. Remove standard outer XML/HTML metadata tags
text = re.sub(r"<ADDITIONAL_METADATA>.*?</ADDITIONAL_METADATA>", "", text, flags=re.DOTALL)
text = re.sub(r"<USER_SETTINGS_CHANGE>.*?</USER_SETTINGS_CHANGE>", "", text, flags=re.DOTALL)
text = re.sub(r"<SYSTEM_MESSAGE>.*?</SYSTEM_MESSAGE>", "", text, flags=re.DOTALL)
text = re.sub(r"<[^>]+>", "", text)
return text.strip()
# 3. Remove delimited system instruction blocks
text = re.sub(r"<!--\s*SYSTEM_INSTRUCTIONS_START\s*-->.*?<!--\s*SYSTEM_INSTRUCTIONS_END\s*-->", "", text, flags=re.DOTALL)
text = re.sub(r"\[SYSTEM_INSTRUCTIONS_BLOCK\].*?\[/SYSTEM_INSTRUCTIONS_BLOCK\]", "", text, flags=re.DOTALL)
# 4. Remove known structured system instructions (backward compatibility)
# Language instruction
text = re.sub(r"\[SYSTEM INSTRUCTION:\s*You MUST respond and communicate in [^\]]+\]", "", text, flags=re.DOTALL)
# Flash auto reasoning mode block
text = re.sub(r"\[SYSTEM INSTRUCTION:\s*GEMINI 3\.7 FLASH AUTO REASONING MODE\].*?(?:• آدرس مخزن:[^\n]*|\Z)", "", text, flags=re.DOTALL)
# Bot actions block
text = re.sub(r"\[SYSTEM INSTRUCTION:\s*TELEGRAM BOT ACTIONS & FUNCTION CALLING\].*?(?:Always explain to the user in a friendly and professional tone what actions have been performed\.?|\Z)", "", text, flags=re.DOTALL)
# Memory instructions block
text = re.sub(r"\[SYSTEM INSTRUCTION:\s*AI PERSISTENT HIERARCHICAL MEMORY & CONTINUOUS LEARNING\].*?(?:5\. Do NOT store temporary or trivial small-talk\. Only store enduring and valuable knowledge\.?|\Z)", "", text, flags=re.DOTALL)
# General fallback for any remaining [SYSTEM INSTRUCTION: ...]
text = re.sub(r"\[SYSTEM INSTRUCTION:[^\]]+\]", "", text)
text = re.sub(r"(?:^|\n)System Instruction:\s*", "", text)
# Clean uploaded file notifications
caption_m = re.search(r"\[User uploaded [^\]]+\]\s*(?:Caption:\s*(.*))?", text, re.DOTALL)
if caption_m:
cap = (caption_m.group(1) or "").strip()
if cap:
text = f"📎 {cap}"
else:
text = "📎 [ارسال فایل]"
# Clean ANSI codes
text = ANSI_REGEX.sub("", text)
# Collapse multi-lines to clean single-line text
lines = [line.strip() for line in text.splitlines() if line.strip()]
return " ".join(lines).strip()
def get_conversation_metadata(conversation_id: str) -> Dict[str, Any]:
"""
@@ -1163,7 +1270,14 @@ def get_conversation_metadata(conversation_id: str) -> Dict[str, Any]:
if user_inputs:
res["created_at"] = user_inputs[0].get("created_at")
first_raw = user_inputs[0].get("content", "")
res["first_prompt"] = clean_user_prompt(first_raw)
first_cleaned = clean_user_prompt(first_raw)
if not first_cleaned and len(user_inputs) > 1:
for u in user_inputs[1:]:
nxt_clean = clean_user_prompt(u.get("content", ""))
if nxt_clean:
first_cleaned = nxt_clean
break
res["first_prompt"] = first_cleaned or "(بدون متن)"
last_raw = user_inputs[-1].get("content", "")
res["last_prompt"] = clean_user_prompt(last_raw)
@@ -1348,7 +1462,12 @@ class AGYEngine:
if system_instructions:
effective_prompt = "\n\n".join(system_instructions) + "\n\n" + prompt
effective_prompt = (
"<!-- SYSTEM_INSTRUCTIONS_START -->\n"
+ "\n\n".join(system_instructions)
+ "\n<!-- SYSTEM_INSTRUCTIONS_END -->\n\n"
+ prompt
)
# Real CLI model name to pass to AGY CLI
cli_model = "gemini-3.7-flash" if target_model in ("gemini-3.7-flash-auto", "gemini-3.7-flash") else target_model
@@ -1584,11 +1703,16 @@ class AGYEngine:
on_delta: Optional[Callable[[str], Any]] = None,
on_thought: Optional[Callable[[str], Any]] = None,
on_tool: Optional[Callable[[str, str], Any]] = None,
on_model_change: Optional[Callable[[str, Optional[str]], Any]] = None,
on_reset: Optional[Callable[[], Any]] = None,
) -> AgentResult:
"""Runs prompt using the natively authenticated AGY CLI engine with real-time streaming and Auto-Effort escalation."""
is_auto_mode = (session.model == "gemini-3.7-flash-auto")
if is_auto_mode:
if on_model_change:
on_model_change("gemini-3.7-flash-auto", "medium")
# First attempt in Auto mode: Start with Medium effort by default
result, esc_effort, esc_reason = await cls._execute_single_run(
session=session,
@@ -1605,6 +1729,12 @@ class AGYEngine:
target_eff = esc_effort if esc_effort in ("low", "medium", "high") else "medium"
logger.info(f"Auto-adjusting effort for chat {session.chat_id} to {target_eff} (reason: {esc_reason})")
if on_reset:
on_reset()
if on_model_change:
on_model_change("gemini-3.7-flash-auto", target_eff)
is_fa = (session.language or "").lower() in ("fa", "farsi", "persian", "🇮🇷 persian / farsi (فارسی)")
if on_thought:
if target_eff == "low":
@@ -1647,6 +1777,9 @@ class AGYEngine:
return result
# Standard execution for all other models
if on_model_change:
on_model_change(session.model, session.effort)
result, _, _ = await cls._execute_single_run(
session=session,
prompt=prompt,
+31 -1
View File
@@ -890,6 +890,32 @@ async def execute_action(
ok, msg, count = session_manager.clear_project_conversations(chat_id)
return f"\n\n🗑️ {msg}", side_effects, created_task
# -------------------------------------------------------------
# 13.5 SET_CONVERSATION_TITLE / RENAME_CONVERSATION / SET_TOPIC
# -------------------------------------------------------------
elif act in ("SET_CONVERSATION_TITLE", "SET_CONV_TITLE", "RENAME_CONVERSATION", "RENAME_CONV", "SET_TOPIC", "CONV_TITLE"):
title = attrs.get("title") or attrs.get("name") or attrs.get("topic") or attrs.get("_default", "")
conv_id = attrs.get("id") or attrs.get("conv_id")
proj = attrs.get("project") or attrs.get("proj") or current_project_name
if not title:
return "\n⚠️ عنوان جدید گفتگو مشخص نشده است." if is_fa else "\n⚠️ Conversation title not specified.", side_effects, created_task
ok, msg, matched_id = session_manager.set_conversation_title(chat_id, title=title, conv_id=conv_id, project_name=proj)
if not ok:
return f"\n⚠️ {msg}", side_effects, created_task
badge = (
f"\n\n🏷️ <b>موضوع گفتگو به‌روزرسانی شد:</b>\n"
f"• 💬 <b>شناسه:</b> <code>{matched_id[:8]}...</code>\n"
f"• 📝 <b>عنوان جدید:</b> «{escape_html(title)}»"
if is_fa else
f"\n\n🏷️ <b>Conversation Topic Updated:</b>\n"
f"• 💬 <b>ID:</b> <code>{matched_id[:8]}...</code>\n"
f"• 📝 <b>New Title:</b> \"{escape_html(title)}\""
)
return badge, side_effects, created_task
# -------------------------------------------------------------
# 14. SCHEDULE_TASK / SCHEDULE / CREATE_TASK
# -------------------------------------------------------------
@@ -1736,7 +1762,11 @@ def get_ai_bot_actions_instruction(lang: str = "fa") -> str:
" [[SWITCH_CONVERSATION: id=\"<conv_id_or_empty_for_last>\"]]\n"
" [[DELETE_CONVERSATION: id=\"<conv_id>\"]]\n"
" [[CLEAR_CONVERSATIONS: project=\"<project_optional>\"]]\n\n"
"12. Schedule Task / Timers / Cron:\n"
"12. Update / Change Conversation Topic & Title (Dynamic Conversation Naming):\n"
" Whenever the user changes topic, starts a distinct new task, or asks to set the conversation name:\n"
" [[SET_CONVERSATION_TITLE: title=\"<concise_topic_title>\", id=\"<conv_id_optional>\", project=\"<project_optional>\"]]\n"
" Example: [[SET_CONVERSATION_TITLE: title=\"پیاده‌سازی ماژول پرداخت\"]]\n\n"
"13. Schedule Task / Timers / Cron:\n"
" [[SCHEDULE_TASK: timing=\"<timing_spec>\", max_runs=<count_or_null>, type=\"prompt|command|reminder\", title=\"<short_title>\", content=\"<exact_prompt_or_command>\"]]\n"
" Timing examples: 'every 10m', 'in 15m', '14:30', 'cron: 0 23 * * *', 'every 1h (3 times)'.\n"
" Types: 'prompt' (AI run), 'command' (bash script), 'reminder' (plain notification).\n"
File diff suppressed because one or more lines are too long