Ensure reasoning effort is always explicitly displayed with model name
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 235 KiB |
Reference in New Issue
Block a user