fix: rebuild system prompt from scratch on every turn

This commit is contained in:
zhayujie
2026-03-23 21:27:44 +08:00
parent 22b8ca0095
commit 13f5fde4fb
2 changed files with 24 additions and 131 deletions

View File

@@ -376,9 +376,9 @@ def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
"", "",
"以下文件在会话启动时**已经自动加载**到系统提示词的「项目上下文」section 中,你**无需再用 read 工具读取它们**", "以下文件在会话启动时**已经自动加载**到系统提示词的「项目上下文」section 中,你**无需再用 read 工具读取它们**",
"", "",
"- ✅ `AGENT.md`: 已加载 - 你的人格和灵魂设定。当你的名字、性格或交流风格发生变化时,主动用 `edit` 更新此文件", "- ✅ `AGENT.md`: 已加载 - 你的人格和灵魂设定,请严格遵循。当你的名字、性格或交流风格发生变化时,主动用 `edit` 更新此文件",
"- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件", "- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件",
"- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则", "- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则,请严格遵循",
"", "",
"**交流规范**:", "**交流规范**:",
"", "",
@@ -423,7 +423,7 @@ def _build_context_files_section(context_files: List[ContextFile], language: str
] ]
if has_agent: if has_agent:
lines.append("**`AGENT.md` 是你的灵魂文件**:严格体现其中定义的人格、语气和设定,避免僵硬、模板化的回复。") lines.append("**`AGENT.md` 是你的灵魂文件**:严格遵循其中定义的人格、规则、语气和设定,避免僵硬、模板化的回复。")
lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。") lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。")
lines.append("") lines.append("")

View File

@@ -100,138 +100,31 @@ class Agent:
def get_full_system_prompt(self, skill_filter=None) -> str: def get_full_system_prompt(self, skill_filter=None) -> str:
""" """
Get the full system prompt including skills. Build the complete system prompt from scratch every time.
Note: Skills are now built into the system prompt by PromptBuilder, Re-reads AGENT.md / USER.md / RULE.md from disk, refreshes skills,
so we just return the base prompt directly. This method is kept for tools, and runtime info so any change takes effect immediately.
backward compatibility. Falls back to the cached self.system_prompt on error.
:param skill_filter: Optional list of skill names to include (deprecated)
:return: Complete system prompt
"""
prompt = self.system_prompt
# Rebuild tool list section to reflect current self.tools
prompt = self._rebuild_tool_list_section(prompt)
# If runtime_info contains dynamic time function, rebuild runtime section
if self.runtime_info and callable(self.runtime_info.get('_get_current_time')):
prompt = self._rebuild_runtime_section(prompt)
# Rebuild skills section to pick up newly installed/removed skills
if self.skill_manager:
prompt = self._rebuild_skills_section(prompt)
return prompt
def _rebuild_runtime_section(self, prompt: str) -> str:
"""
Rebuild runtime info section with current time.
This method dynamically updates the runtime info section by calling
the _get_current_time function from runtime_info.
:param prompt: Original system prompt
:return: Updated system prompt with current runtime info
""" """
try: try:
# Get current time dynamically from agent.prompt import load_context_files, PromptBuilder
time_info = self.runtime_info['_get_current_time']()
if self.skill_manager:
# Build new runtime section self.skill_manager.refresh_skills()
runtime_lines = [
"\n## 运行时信息\n", context_files = load_context_files(self.workspace_dir) if self.workspace_dir else None
"\n",
f"当前时间: {time_info['time']} {time_info['weekday']} ({time_info['timezone']})\n", builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language="zh")
"\n" return builder.build(
] tools=self.tools,
context_files=context_files,
# Add other runtime info skill_manager=self.skill_manager,
runtime_parts = [] memory_manager=self.memory_manager,
if self.runtime_info.get("model"): runtime_info=self.runtime_info,
runtime_parts.append(f"模型={self.runtime_info['model']}") )
if self.runtime_info.get("workspace"):
# Replace backslashes with forward slashes for Windows paths
workspace_path = str(self.runtime_info['workspace']).replace('\\', '/')
runtime_parts.append(f"工作空间={workspace_path}")
if self.runtime_info.get("channel") and self.runtime_info.get("channel") != "web":
runtime_parts.append(f"渠道={self.runtime_info['channel']}")
if runtime_parts:
runtime_lines.append("运行时: " + " | ".join(runtime_parts) + "\n")
runtime_lines.append("\n")
new_runtime_section = "".join(runtime_lines)
# Find and replace the runtime section
import re
pattern = r'\n## 运行时信息\s*\n.*?(?=\n##|\Z)'
_repl = new_runtime_section.rstrip('\n')
updated_prompt = re.sub(pattern, lambda m: _repl, prompt, flags=re.DOTALL)
return updated_prompt
except Exception as e: except Exception as e:
logger.warning(f"Failed to rebuild runtime section: {e}") logger.warning(f"Failed to rebuild system prompt, using cached version: {e}")
return prompt return self.system_prompt
def _rebuild_skills_section(self, prompt: str) -> str:
"""
Rebuild the <available_skills> block so that newly installed or
removed skills are reflected without re-creating the agent.
"""
try:
import re
self.skill_manager.refresh_skills()
new_skills_xml = self.skill_manager.build_skills_prompt()
old_block_pattern = r'<available_skills>.*?</available_skills>'
has_old_block = re.search(old_block_pattern, prompt, flags=re.DOTALL)
# Extract the new <available_skills>...</available_skills> tag from the prompt
new_block = ""
if new_skills_xml and new_skills_xml.strip():
m = re.search(old_block_pattern, new_skills_xml, flags=re.DOTALL)
if m:
new_block = m.group(0)
if has_old_block:
replacement = new_block or "<available_skills>\n</available_skills>"
# Use lambda to prevent re.sub from interpreting backslashes in replacement
# (e.g. Windows paths like \LinkAI would be treated as bad escape sequences)
prompt = re.sub(old_block_pattern, lambda m: replacement, prompt, flags=re.DOTALL)
elif new_block:
skills_header = "以下是可用技能:"
idx = prompt.find(skills_header)
if idx != -1:
insert_pos = idx + len(skills_header)
prompt = prompt[:insert_pos] + "\n" + new_block + prompt[insert_pos:]
except Exception as e:
logger.warning(f"Failed to rebuild skills section: {e}")
return prompt
def _rebuild_tool_list_section(self, prompt: str) -> str:
"""
Rebuild the tool list inside the '## 工具系统' section so that it
always reflects the current ``self.tools`` (handles dynamic add/remove
of conditional tools like web_search).
"""
import re
from agent.prompt.builder import _build_tooling_section
try:
if not self.tools:
return prompt
new_lines = _build_tooling_section(self.tools, "zh")
new_section = "\n".join(new_lines).rstrip("\n")
# Replace existing tooling section
pattern = r'## 工具系统\s*\n.*?(?=\n## |\Z)'
updated = re.sub(pattern, lambda m: new_section, prompt, count=1, flags=re.DOTALL)
return updated
except Exception as e:
logger.warning(f"Failed to rebuild tool list section: {e}")
return prompt
def refresh_skills(self): def refresh_skills(self):
"""Refresh the loaded skills.""" """Refresh the loaded skills."""