fix(aibox): fix prompt ordering, reduce per-turn token waste, and improve defaults
Prompt system bugs and improvements:
1. **SKILL_PREFIX bug fixed**: SKILL_PREFIX = "1_" only matched files starting
with "1_", causing 2_ through 10_ skill prompt files to be misclassified
as "other" files and sorted in wrong order (e.g., 10_task_handling.md
sorted before 2_memory_save.md due to lexicographic ordering). Replaced
with regex _SKILL_FILE_RE = re.compile(r'^(\d+)_') and _skill_sort_key()
that extracts the numeric prefix for proper numeric sorting.
2. **_init_system_prompts used unsorted list**: get_prompts_list() returned
prompts in os.listdir() lexicographic order, which put 100_STATUS.md
before 1_memory_search.md and 10_task_handling.md before 1_memory_search.md.
Now uses get_sorted_prompts_list() for correct ordering:
identity → skill prompts (1-10 by number) → soul → status.
3. **Verbose pre_prompt injected every turn (~30 lines)**: The pre_prompt
contained detailed workflow instructions (search_skills first, memory
search, ask_user, etc.) that duplicated what system skill prompts
already say, wasting ~300+ tokens per user message. Worse, the same
verbose prompt was injected on recursive think_and_respond calls after
tool execution, confusing the model with repeated "第一步搜索技能"
instructions when it was already mid-conversation with tool results.
- Reduced pre_prompt to 5 concise bullet points as a reminder
- Added is_continuation parameter: recursive calls after tool execution
skip pre_prompt entirely and just pass "请基于以上工具执行结果继续回答。"
- Removed post_prompt (save_memory/update_soul instructions already in
2_memory_save.md and 8_identity_management.md)
4. **Unreasonable large-file thresholds**: 3_file_operations.md default
said >50KB or >200 lines is "too large" — too conservative for code
files. Raised to 500KB/2000 lines; added tail=true and binary file
guidance.
5. **Overly rigid memory search instruction**: 1_memory_search.md said
"每次都要做!" (must do every time), causing unnecessary memory searches
for simple greetings/acknowledgments. Softened to "根据话题相关性判断",
with explicit examples of when to skip (greetings, confirmations).
New/changed methods:
- PromptManager._skill_sort_key(cls, filename) - extract numeric prefix
- PromptManager._prompt_sort_key(self, p) - tuple-based sort key
- PromptManager.get_sorted_prompts_list() - returns correctly ordered list
- PromptManager.get_skill_prompts() - uses regex instead of startswith("1_")
- DeepSeekChat.think_and_respond() - added is_continuation parameter
This commit is contained in:
1 parent
da7f11d352
commit
e8fa26d79f
1 file changed
+77
-94
+77
-94
@@ -436,7 +436,7 @@ class PromptManager:
|
||||
|
||||
文件命名规范:
|
||||
- 00_IDENTITY.md: 用户身份信息
|
||||
- 1_*.md/txt: 基础技能(必须从1_开头)
|
||||
- N_*.md/txt: 基础技能提示词(N 为数字,如 1_memory_search.md、10_task_handling.md),按数字升序加载
|
||||
- 99_SOUL.md: 智能体灵魂文件(角色定位、身份定义、性格等)
|
||||
- 100_STATUS.md: 智能体状态记录
|
||||
"""
|
||||
@@ -450,8 +450,8 @@ class PromptManager:
|
||||
STATUS_FILE = "100_STATUS.md"
|
||||
SOUL_FILE = "99_SOUL.md" # 智能体灵魂文件 - 所有角色定位内容
|
||||
|
||||
# 基础技能前缀
|
||||
SKILL_PREFIX = "1_" # 基础技能文件必须以这个开头
|
||||
# 基础技能文件名模式:N_xxx.md(N 为数字,如 1_memory_search.md、10_task_handling.md)
|
||||
_SKILL_FILE_RE = re.compile(r'^(\d+)_')
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
@@ -642,14 +642,17 @@ class PromptManager:
|
||||
print(f"⚠️ 创建默认灵魂文件失败: {e}")
|
||||
|
||||
def _ensure_skill_files(self):
|
||||
"""确保基础技能文件存在 - 以1_开头的文件"""
|
||||
"""确保基础技能文件存在 - N_ 开头的文件(N 为数字,按数字顺序加载)"""
|
||||
skill_files = [
|
||||
{
|
||||
'filename': '1_memory_search.md',
|
||||
'content': """# 记忆搜索习惯
|
||||
|
||||
## 重要习惯
|
||||
在每次回答用户问题前,**必须**先使用 `memory search` 工具搜索记忆库中与当前话题相关的记忆。
|
||||
## 核心习惯
|
||||
在回答用户问题前,**应根据话题相关性判断是否需要搜索记忆**:
|
||||
- 涉及过往讨论、用户偏好、之前做过的事 → 必须搜索
|
||||
- 全新话题、简单问候("你好")、确认回应("好的""明白了")→ 无需搜索
|
||||
- 不确定时,搜索一下也无妨
|
||||
|
||||
## 为什么要这样做?
|
||||
- 保持对话的连贯性
|
||||
@@ -665,11 +668,9 @@ memory search keywords=["关键词1", "关键词2"]
|
||||
## 示例
|
||||
如果用户说:"上次我们讨论的那个项目怎么样了?"
|
||||
你应该:
|
||||
1. 先搜索 "项目" 相关的记忆
|
||||
1. 搜索 "项目" 相关的记忆
|
||||
2. 根据搜索结果回答
|
||||
3. 如果找不到相关记忆,诚实告知用户
|
||||
|
||||
记住:这是你的核心习惯,每次都要做!
|
||||
"""
|
||||
},
|
||||
{
|
||||
@@ -711,19 +712,22 @@ save_memory_and_end_conversation(
|
||||
- **list_files**: 列出目录
|
||||
|
||||
## 大文件处理规则
|
||||
当遇到以下情况时,**必须先告知用户文件过大**:
|
||||
1. 文件大小超过 50KB
|
||||
2. 文件行数超过 200 行
|
||||
当遇到以下情况时,**必须分页读取**,不要一次性读取整个文件:
|
||||
1. 文件大小超过 500KB
|
||||
2. 文件行数超过 2000 行
|
||||
|
||||
对于大文件,应该:
|
||||
- 使用 head/tail 参数只读取开头或结尾
|
||||
- 先用 head=true 读取前50行了解文件结构
|
||||
- 使用 start_line/end_line 参数指定行范围
|
||||
- 建议用户提供更具体的查询条件
|
||||
- 使用 tail=true 读取文件末尾
|
||||
- 对于二进制文件或特殊格式文件,使用 run_command 配合 head/strings 等命令查看
|
||||
- 建议用户提供更具体的查询条件以缩小范围
|
||||
|
||||
## 示例
|
||||
```
|
||||
read_file filepath="/path/to/file.txt" head=true max_lines=50
|
||||
read_file filepath="/path/to/file.txt" start_line=10 end_line=20
|
||||
read_file filepath="/path/to/file.txt" start_line=100 end_line=200
|
||||
read_file filepath="/path/to/file.txt" tail=true max_lines=50
|
||||
```
|
||||
"""
|
||||
},
|
||||
@@ -1047,42 +1051,55 @@ update_soul content="我是...(新的自我定义)"
|
||||
"""创建默认提示词(已由各 _ensure_* 方法处理)"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _skill_sort_key(cls, filename: str):
|
||||
"""技能文件按前缀数字排序,非技能文件返回 None。"""
|
||||
m = cls._SKILL_FILE_RE.match(filename)
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
def _prompt_sort_key(self, p):
|
||||
"""排序规则:00_IDENTITY → 技能文件(按数字升序) → 99_SOUL → 100_STATUS → 其他"""
|
||||
file = p['file']
|
||||
if file == self.IDENTITY_FILE:
|
||||
return (0,)
|
||||
skill_num = self._skill_sort_key(file)
|
||||
if skill_num is not None:
|
||||
return (1, skill_num)
|
||||
if file == self.SOUL_FILE:
|
||||
return (2,)
|
||||
if file == self.STATUS_FILE:
|
||||
return (4,)
|
||||
return (3, file) # 其他文件按名字排序
|
||||
|
||||
def get_prompts(self, join_with: str = "\n\n") -> str:
|
||||
"""获取所有提示词,按顺序拼接"""
|
||||
"""获取所有提示词,按顺序拼接(identity → 技能文件 → soul → status)"""
|
||||
if not self._prompts:
|
||||
return ""
|
||||
|
||||
# 排序规则:先基础技能(1_开头),再其他,最后灵魂文件
|
||||
def sort_key(p):
|
||||
file = p['file']
|
||||
if file == self.SOUL_FILE:
|
||||
return 3 # 灵魂文件最后加载
|
||||
elif file.startswith(self.SKILL_PREFIX):
|
||||
return 1 # 基础技能
|
||||
elif file == self.IDENTITY_FILE:
|
||||
return 0 # 身份文件最先
|
||||
elif file == self.STATUS_FILE:
|
||||
return 4 # 状态文件最后
|
||||
else:
|
||||
return 2
|
||||
|
||||
sorted_prompts = sorted(self._prompts, key=sort_key)
|
||||
sorted_prompts = sorted(self._prompts, key=self._prompt_sort_key)
|
||||
contents = [p['content'] for p in sorted_prompts]
|
||||
return join_with.join(contents)
|
||||
|
||||
|
||||
def get_sorted_prompts_list(self) -> List[Dict]:
|
||||
"""获取按正确顺序排列的提示词列表(用于 system 消息构建)。"""
|
||||
return sorted(self._prompts, key=self._prompt_sort_key)
|
||||
|
||||
def get_prompts_list(self) -> List[Dict]:
|
||||
"""获取所有提示词列表"""
|
||||
"""获取所有提示词列表(原始加载顺序,已弃用,请用 get_sorted_prompts_list)。"""
|
||||
return self._prompts.copy()
|
||||
|
||||
|
||||
def get_skill_prompts(self) -> List[Dict]:
|
||||
"""获取所有基础技能提示词(N_ 开头的文件)。"""
|
||||
return [p for p in self._prompts if self._skill_sort_key(p['file']) is not None]
|
||||
|
||||
def reload(self):
|
||||
"""重新加载所有提示词"""
|
||||
self._prompts = []
|
||||
self._load_prompts()
|
||||
return len(self._prompts)
|
||||
|
||||
|
||||
def get_prompt_count(self) -> int:
|
||||
return len(self._prompts)
|
||||
|
||||
|
||||
def get_prompts_dir_path(self) -> str:
|
||||
return self._prompts_dir
|
||||
|
||||
@@ -6536,19 +6553,19 @@ class DeepSeekChat:
|
||||
self._register_tools(exit_callback, command_executor)
|
||||
|
||||
def _init_system_prompts(self, system_prompt: str = None):
|
||||
"""初始化系统提示词 - 从 PromptManager 加载"""
|
||||
"""初始化系统提示词 - 从 PromptManager 加载(按 identity → 技能 → soul → status 顺序)"""
|
||||
if system_prompt:
|
||||
# 如果明确指定了 system_prompt,使用它
|
||||
self._add_message("system", system_prompt)
|
||||
self.logger.info("使用指定的系统提示词")
|
||||
else:
|
||||
# 从 PromptManager 加载所有提示词
|
||||
prompts = self.prompt_manager.get_prompts_list()
|
||||
# 从 PromptManager 加载所有提示词,按正确顺序排列
|
||||
prompts = self.prompt_manager.get_sorted_prompts_list()
|
||||
if prompts:
|
||||
for prompt in prompts:
|
||||
if prompt['content'].strip():
|
||||
self._add_message("system", prompt['content'].strip())
|
||||
self.logger.info(f"已加载 {len(prompts)} 个系统提示词")
|
||||
self.logger.info(f"已加载 {len(prompts)} 个系统提示词(顺序: identity → 技能 → soul → status)")
|
||||
else:
|
||||
# 如果没有任何提示词,添加一个默认的
|
||||
default_prompt = "你是一个智能AI助手。"
|
||||
@@ -6847,8 +6864,15 @@ class DeepSeekChat:
|
||||
# 默认情况下,如果记忆库存在,搜索一下也无妨
|
||||
return True
|
||||
|
||||
def think_and_respond(self, input_text: str, output_callback: Callable = None) -> Optional[str]:
|
||||
"""思考并回应 - 支持实时流式输出,支持思考模式"""
|
||||
def think_and_respond(self, input_text: str, output_callback: Callable = None, is_continuation: bool = False) -> Optional[str]:
|
||||
"""思考并回应 - 支持实时流式输出,支持思考模式。
|
||||
|
||||
Args:
|
||||
input_text: 用户输入或继续提示
|
||||
output_callback: 流式输出回调
|
||||
is_continuation: 是否为工具调用后的递归继续;为 True 时不注入冗长的工作流指令,
|
||||
因为 system prompt 中已有完整行为规范,继续时只需传递工具结果
|
||||
"""
|
||||
if not self.conversation_active:
|
||||
self.logger.info("对话已结束,重置状态")
|
||||
self.conversation_active = True
|
||||
@@ -6880,54 +6904,14 @@ class DeepSeekChat:
|
||||
|
||||
self.stats["api_calls"] += 1
|
||||
|
||||
# 前置提示词
|
||||
pre_prompt = """【系统指令 - 核心工作流程】
|
||||
|
||||
在开始任何任务之前,你必须严格按照以下优先级执行:
|
||||
|
||||
## 第一步:搜索可用技能(最高优先级)⭐
|
||||
当用户提出任何任务、需求或问题时,**必须先使用 `search_skills` 工具**搜索相关技能:
|
||||
- 使用 `search_skills keyword="[任务关键词]"` 搜索是否有现成的扩展技能可以完成该任务
|
||||
- 例如:用户说"帮我查天气",你应该先执行 `search_skills keyword="天气"`
|
||||
- 例如:用户说"帮我计算BMI",你应该先执行 `search_skills keyword="BMI"`
|
||||
- 如果搜索结果中有匹配的技能,优先使用该技能完成任务
|
||||
- 只有在没有合适技能的情况下,才考虑使用其他工具或方法
|
||||
|
||||
## 第二步:搜索相关记忆
|
||||
在每次回答用户问题前,**必须**使用 `memory search` 工具搜索记忆库中与当前话题相关的记忆,确保回答的连贯性和准确性。
|
||||
|
||||
## 第三步:积极向用户提问
|
||||
如果你有任何无法确定的事情,那么你需要用户提供额外信息,
|
||||
请务必使用 `ask_user` 工具向用户提问,以便获得更准确的指引。
|
||||
|
||||
## 第四步:执行任务或回答问题
|
||||
根据搜索结果,选择合适的工具或直接回答。
|
||||
|
||||
【重要提醒】
|
||||
- 技能优先原则:有技能先用技能,不要自己硬编码实现
|
||||
- 如果没有合适的技能,可以使用其他工具或直接回答
|
||||
- 对话结束时,必须使用 `save_memory_and_end_conversation` 工具保存本次对话的重要信息并更新智能体状态
|
||||
- 当了解到用户的身份信息时,使用 `update_identity` 工具更新 00_IDENTITY.md
|
||||
- 当你的角色、性格、能力边界需要调整时,使用 `update_soul` 工具更新 99_SOUL.md 文件来重新定义自己
|
||||
- 绝对不要直接打开大文件来读,应该使用额外的压缩工具
|
||||
|
||||
现在请处理用户的输入:"""
|
||||
|
||||
# 后置提示词
|
||||
post_prompt = """
|
||||
|
||||
【注意】
|
||||
当本次对话的话题讨论完成时,请使用 `save_memory_and_end_conversation` 工具结束对话并保存重要信息到记忆库,同时更新你的状态到 100_STATUS.md。
|
||||
|
||||
【自我认知更新】
|
||||
在对话过程中,如果你发现:
|
||||
- 自己的角色定位需要调整
|
||||
- 性格特点需要改变
|
||||
- 能力边界需要重新定义
|
||||
- 行为准则需要优化
|
||||
请使用 `update_soul` 工具更新 99_SOUL.md 文件,这有助于你更好地理解自己并为用户提供更精准的服务。"""
|
||||
|
||||
enhanced_input = pre_prompt + "\n\n" + input_text + post_prompt
|
||||
if is_continuation:
|
||||
# 递归继续(工具调用后):直接使用输入文本,不重复注入工作流指令
|
||||
# system prompt 中已有完整行为规范,此处只需传递工具结果的上下文
|
||||
enhanced_input = input_text
|
||||
else:
|
||||
# 首轮回话:附加简洁的工作流程提醒(不重复 system prompt 中已有的详细说明)
|
||||
pre_prompt = "【工作流程提醒】\n1. 如果用户提出新任务,先用 search_skills 搜索是否有现成技能可用\n2. 根据话题相关性决定是否 memory search(简单问候/确认无需搜索)\n3. 信息不足时用 ask_user 提问,不要猜测\n4. 遇到大文件用分页读取(head/tail/start_line/end_line),不要直接打开大文件\n5. 话题完成时用 save_memory_and_end_conversation 保存记忆并结束"
|
||||
enhanced_input = pre_prompt + "\n\n" + input_text
|
||||
|
||||
# 记录本轮开始前的消息长度,用于 API 失败时回滚,防止坏消息污染对话历史
|
||||
msgs_before_turn = len(self.messages)
|
||||
@@ -7146,9 +7130,8 @@ class DeepSeekChat:
|
||||
if output_callback:
|
||||
output_callback("line", i18n.get('analyzing_tool_results') + "\n")
|
||||
|
||||
# 【关键修复4】递归调用,让AI基于工具结果继续回答
|
||||
# 使用一个新的输入提示,避免重复
|
||||
return self.think_and_respond("请基于以上工具执行结果继续回答用户的问题。", output_callback)
|
||||
# 递归调用,让AI基于工具结果继续回答;is_continuation=True 避免重复注入工作流指令
|
||||
return self.think_and_respond("请基于以上工具执行结果继续回答。", output_callback, is_continuation=True)
|
||||
|
||||
elif full_response:
|
||||
# 正常添加助手消息,包含 reasoning_content
|
||||
|
||||
Reference in new issue
Block a user