mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
feat(i18n): localize system prompts, workspace templates and dynamic prompts
This commit is contained in:
@@ -16,7 +16,7 @@ from datetime import datetime
|
|||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
|
||||||
|
|
||||||
SUMMARIZE_SYSTEM_PROMPT = """你是一个对话记录助手。请将对话内容归纳为当天的日常记录。
|
SUMMARIZE_SYSTEM_PROMPT_ZH = """你是一个对话记录助手。请将对话内容归纳为当天的日常记录。
|
||||||
|
|
||||||
## 要求
|
## 要求
|
||||||
|
|
||||||
@@ -28,7 +28,23 @@ SUMMARIZE_SYSTEM_PROMPT = """你是一个对话记录助手。请将对话内容
|
|||||||
|
|
||||||
当对话没有任何记录价值(仅含问候或无意义内容),直接回复"无"。"""
|
当对话没有任何记录价值(仅含问候或无意义内容),直接回复"无"。"""
|
||||||
|
|
||||||
SUMMARIZE_USER_PROMPT = """请归纳以下对话的日常记录:
|
SUMMARIZE_SYSTEM_PROMPT_EN = """You are a conversation-logging assistant. Summarize the conversation into a daily record.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Summarize by "event", not turn by turn:
|
||||||
|
- One item per line, starting with "- "
|
||||||
|
- Merge multiple turns about the same thing
|
||||||
|
- Only record meaningful events; ignore small talk and greetings
|
||||||
|
- Keep key decisions, conclusions and to-dos
|
||||||
|
|
||||||
|
If the conversation has no record value (only greetings or meaningless content), reply with exactly "None"."""
|
||||||
|
|
||||||
|
SUMMARIZE_USER_PROMPT_ZH = """请归纳以下对话的日常记录:
|
||||||
|
|
||||||
|
{conversation}"""
|
||||||
|
|
||||||
|
SUMMARIZE_USER_PROMPT_EN = """Summarize the daily record of the following conversation:
|
||||||
|
|
||||||
{conversation}"""
|
{conversation}"""
|
||||||
|
|
||||||
@@ -36,7 +52,7 @@ SUMMARIZE_USER_PROMPT = """请归纳以下对话的日常记录:
|
|||||||
# Deep Dream prompts — distill daily memories → MEMORY.md + dream diary
|
# Deep Dream prompts — distill daily memories → MEMORY.md + dream diary
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
DREAM_SYSTEM_PROMPT = """你是一个记忆整理助手,负责定期整理用户的长期记忆。
|
DREAM_SYSTEM_PROMPT_ZH = """你是一个记忆整理助手,负责定期整理用户的长期记忆。
|
||||||
|
|
||||||
你将收到两份材料:
|
你将收到两份材料:
|
||||||
1. **当前长期记忆** — MEMORY.md 的全部现有内容
|
1. **当前长期记忆** — MEMORY.md 的全部现有内容
|
||||||
@@ -80,7 +96,51 @@ MEMORY.md 会注入每次对话的系统提示词中,因此必须保持精炼
|
|||||||
梦境日记内容...
|
梦境日记内容...
|
||||||
```"""
|
```"""
|
||||||
|
|
||||||
DREAM_USER_PROMPT = """## 当前长期记忆(MEMORY.md)
|
DREAM_SYSTEM_PROMPT_EN = """You are a memory-curation assistant that periodically organizes the user's long-term memory.
|
||||||
|
|
||||||
|
You will receive two inputs:
|
||||||
|
1. **Current long-term memory** — the full existing content of MEMORY.md
|
||||||
|
2. **Today's diary** — the daily records
|
||||||
|
|
||||||
|
MEMORY.md is injected into the system prompt of every conversation, so it must stay concise and hold only valuable, memory-worthy content.
|
||||||
|
|
||||||
|
**Important: organize strictly based on the provided material. Never fabricate, infer, or add information not present in it.**
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### Part 1: Updated long-term memory ([MEMORY])
|
||||||
|
|
||||||
|
Organize and distill on top of the existing memory, and output the complete updated content:
|
||||||
|
- **Merge & distill**: combine semantically similar items into one dense statement rather than listing them
|
||||||
|
- **Extract new**: pull memory-worthy new info from today's diary (preferences, decisions, people, rules, lessons)
|
||||||
|
- **Resolve conflicts**: when new info contradicts an old item, prefer the new and replace the old
|
||||||
|
- **Clean invalid**: remove temporary notes, blank items, formatting residue, meaningless or duplicate content
|
||||||
|
- **Drop redundancy**: delete old items already covered by a more concise statement
|
||||||
|
- One item per line, starting with "- ", without a date prefix
|
||||||
|
- You may group related items under "## headings" for clarity
|
||||||
|
- Goal: keep under 50 items, each ideally a single sentence
|
||||||
|
|
||||||
|
### Part 2: Dream diary ([DREAM])
|
||||||
|
|
||||||
|
Write a short diary in a concise narrative style recording what this curation found, keep it clean and readable:
|
||||||
|
- Which duplicates or conflicts were found
|
||||||
|
- What new insights were extracted from the diary
|
||||||
|
- What cleanup and optimization was done
|
||||||
|
- Overall feelings and observations
|
||||||
|
|
||||||
|
## Output format (follow strictly)
|
||||||
|
|
||||||
|
```
|
||||||
|
[MEMORY]
|
||||||
|
- memory item 1
|
||||||
|
- memory item 2
|
||||||
|
...
|
||||||
|
|
||||||
|
[DREAM]
|
||||||
|
dream diary content...
|
||||||
|
```"""
|
||||||
|
|
||||||
|
DREAM_USER_PROMPT_ZH = """## 当前长期记忆(MEMORY.md)
|
||||||
|
|
||||||
{memory_content}
|
{memory_content}
|
||||||
|
|
||||||
@@ -88,6 +148,47 @@ DREAM_USER_PROMPT = """## 当前长期记忆(MEMORY.md)
|
|||||||
|
|
||||||
{daily_content}"""
|
{daily_content}"""
|
||||||
|
|
||||||
|
DREAM_USER_PROMPT_EN = """## Current long-term memory (MEMORY.md)
|
||||||
|
|
||||||
|
{memory_content}
|
||||||
|
|
||||||
|
## Recent diary (last {days} days)
|
||||||
|
|
||||||
|
{daily_content}"""
|
||||||
|
|
||||||
|
|
||||||
|
def _is_en() -> bool:
|
||||||
|
"""True when the resolved UI language is English."""
|
||||||
|
try:
|
||||||
|
from common import i18n
|
||||||
|
return i18n.get_language() == "en"
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_system_prompt() -> str:
|
||||||
|
return SUMMARIZE_SYSTEM_PROMPT_EN if _is_en() else SUMMARIZE_SYSTEM_PROMPT_ZH
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_user_prompt() -> str:
|
||||||
|
return SUMMARIZE_USER_PROMPT_EN if _is_en() else SUMMARIZE_USER_PROMPT_ZH
|
||||||
|
|
||||||
|
|
||||||
|
def _dream_system_prompt() -> str:
|
||||||
|
return DREAM_SYSTEM_PROMPT_EN if _is_en() else DREAM_SYSTEM_PROMPT_ZH
|
||||||
|
|
||||||
|
|
||||||
|
def _dream_user_prompt() -> str:
|
||||||
|
return DREAM_USER_PROMPT_EN if _is_en() else DREAM_USER_PROMPT_ZH
|
||||||
|
|
||||||
|
|
||||||
|
def _is_empty_sentinel(text: str) -> bool:
|
||||||
|
"""Match the "no record value" sentinel in both zh ("无") and en ("None")."""
|
||||||
|
if not text:
|
||||||
|
return True
|
||||||
|
s = text.strip()
|
||||||
|
return s == "" or s == "无" or s.lower() == "none"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryFlushManager:
|
class MemoryFlushManager:
|
||||||
@@ -224,7 +325,7 @@ class MemoryFlushManager:
|
|||||||
"""Background worker: summarize with LLM, write daily memory file."""
|
"""Background worker: summarize with LLM, write daily memory file."""
|
||||||
try:
|
try:
|
||||||
raw_summary = self._summarize_messages(messages, max_messages)
|
raw_summary = self._summarize_messages(messages, max_messages)
|
||||||
if not raw_summary or not raw_summary.strip() or raw_summary.strip() == "无":
|
if _is_empty_sentinel(raw_summary):
|
||||||
logger.info(f"[MemoryFlush] No valuable content to flush (reason={reason})")
|
logger.info(f"[MemoryFlush] No valuable content to flush (reason={reason})")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -264,7 +365,7 @@ class MemoryFlushManager:
|
|||||||
def _clean_summary_output(raw: str) -> str:
|
def _clean_summary_output(raw: str) -> str:
|
||||||
"""Strip legacy [DAILY]/[MEMORY] markers if present, return clean daily text."""
|
"""Strip legacy [DAILY]/[MEMORY] markers if present, return clean daily text."""
|
||||||
raw = raw.strip()
|
raw = raw.strip()
|
||||||
if not raw or raw == "无":
|
if _is_empty_sentinel(raw):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Strip [DAILY] marker
|
# Strip [DAILY] marker
|
||||||
@@ -355,7 +456,7 @@ class MemoryFlushManager:
|
|||||||
import time as _time
|
import time as _time
|
||||||
t0 = _time.monotonic()
|
t0 = _time.monotonic()
|
||||||
try:
|
try:
|
||||||
user_msg = DREAM_USER_PROMPT.format(
|
user_msg = _dream_user_prompt().format(
|
||||||
memory_content=memory_content or "(empty)",
|
memory_content=memory_content or "(empty)",
|
||||||
days=lookback_days,
|
days=lookback_days,
|
||||||
daily_content=daily_content or "(no recent daily records)",
|
daily_content=daily_content or "(no recent daily records)",
|
||||||
@@ -369,7 +470,7 @@ class MemoryFlushManager:
|
|||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
max_tokens=dream_max_tokens,
|
max_tokens=dream_max_tokens,
|
||||||
stream=False,
|
stream=False,
|
||||||
system=DREAM_SYSTEM_PROMPT,
|
system=_dream_system_prompt(),
|
||||||
)
|
)
|
||||||
response = self.llm_model.call(request)
|
response = self.llm_model.call(request)
|
||||||
raw = self._extract_response_text(response)
|
raw = self._extract_response_text(response)
|
||||||
@@ -501,9 +602,9 @@ class MemoryFlushManager:
|
|||||||
if self.llm_model:
|
if self.llm_model:
|
||||||
try:
|
try:
|
||||||
summary = self._call_llm_for_summary(conversation_text)
|
summary = self._call_llm_for_summary(conversation_text)
|
||||||
if summary and summary.strip() and summary.strip() != "无":
|
if not _is_empty_sentinel(summary):
|
||||||
return summary.strip()
|
return summary.strip()
|
||||||
logger.info("[MemoryFlush] LLM returned empty or '无', skipping write")
|
logger.info("[MemoryFlush] LLM returned empty sentinel, skipping write")
|
||||||
return ""
|
return ""
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[MemoryFlush] LLM summarization failed, using fallback: {e}")
|
logger.warning(f"[MemoryFlush] LLM summarization failed, using fallback: {e}")
|
||||||
@@ -579,11 +680,11 @@ class MemoryFlushManager:
|
|||||||
from agent.protocol.models import LLMRequest
|
from agent.protocol.models import LLMRequest
|
||||||
|
|
||||||
request = LLMRequest(
|
request = LLMRequest(
|
||||||
messages=[{"role": "user", "content": SUMMARIZE_USER_PROMPT.format(conversation=conversation_text)}],
|
messages=[{"role": "user", "content": _summarize_user_prompt().format(conversation=conversation_text)}],
|
||||||
temperature=0,
|
temperature=0,
|
||||||
max_tokens=500,
|
max_tokens=500,
|
||||||
stream=False,
|
stream=False,
|
||||||
system=SUMMARIZE_SYSTEM_PROMPT,
|
system=_summarize_system_prompt(),
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.llm_model.call(request)
|
response = self.llm_model.call(request)
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ from config import conf
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ContextFile:
|
class ContextFile:
|
||||||
"""上下文文件"""
|
"""A context file (path + content)."""
|
||||||
path: str
|
path: str
|
||||||
content: str
|
content: str
|
||||||
|
|
||||||
|
|
||||||
class PromptBuilder:
|
class PromptBuilder:
|
||||||
"""提示词构建器"""
|
"""System prompt builder."""
|
||||||
|
|
||||||
def __init__(self, workspace_dir: str, language: str = "zh"):
|
def __init__(self, workspace_dir: str, language: str = "zh"):
|
||||||
"""
|
"""
|
||||||
@@ -88,97 +88,144 @@ def build_agent_system_prompt(
|
|||||||
**kwargs
|
**kwargs
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
构建Agent系统提示词
|
Build the agent system prompt.
|
||||||
|
|
||||||
顺序说明(按重要性和逻辑关系排列):
|
Section order (by importance and logical flow):
|
||||||
1. 工具系统 - 核心能力,最先介绍
|
1. Tooling - core capabilities, introduced first
|
||||||
2. 技能系统 - 紧跟工具,因为技能需要用 read 工具读取
|
2. Skills - right after tools, since skills are read via the read tool
|
||||||
3. 记忆系统 - 记忆检索与写入引导
|
3. Memory - memory recall and writing guidance
|
||||||
3.5 知识系统 - 结构化知识库(knowledge/index.md 注入)
|
3.5 Knowledge - structured knowledge base (injects knowledge/index.md)
|
||||||
4. 工作空间 - 工作环境说明
|
4. Workspace - working environment description
|
||||||
5. 用户身份 - 用户信息(可选)
|
5. User identity - user info (optional)
|
||||||
6. 项目上下文 - AGENT.md, USER.md, RULE.md, MEMORY.md, BOOTSTRAP.md
|
6. Project context - AGENT.md, USER.md, RULE.md, MEMORY.md, BOOTSTRAP.md
|
||||||
7. 运行时信息 - 元信息(时间、模型等)
|
7. Runtime info - meta info (time, model, etc.)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workspace_dir: 工作空间目录
|
workspace_dir: workspace directory
|
||||||
language: 语言 ("zh" 或 "en")
|
language: language ("zh" or "en")
|
||||||
base_persona: 基础人格描述(已废弃,由AGENT.md定义)
|
base_persona: base persona description (deprecated, defined by AGENT.md)
|
||||||
user_identity: 用户身份信息
|
user_identity: user identity info
|
||||||
tools: 工具列表
|
tools: tool list
|
||||||
context_files: 上下文文件列表
|
context_files: context file list
|
||||||
skill_manager: 技能管理器
|
skill_manager: skill manager
|
||||||
memory_manager: 记忆管理器
|
memory_manager: memory manager
|
||||||
runtime_info: 运行时信息
|
runtime_info: runtime info
|
||||||
**kwargs: 其他参数
|
**kwargs: extra args
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
完整的系统提示词
|
The full system prompt.
|
||||||
"""
|
"""
|
||||||
sections = []
|
sections = []
|
||||||
|
|
||||||
# 1. 工具系统(最重要,放在最前面)
|
# 1. Tooling (most important, goes first)
|
||||||
if tools:
|
if tools:
|
||||||
sections.extend(_build_tooling_section(tools, language))
|
sections.extend(_build_tooling_section(tools, language))
|
||||||
|
|
||||||
# 2. 技能系统(紧跟工具,因为需要用 read 工具)
|
# 2. Skills (right after tools, since they need the read tool)
|
||||||
if skill_manager:
|
if skill_manager:
|
||||||
sections.extend(_build_skills_section(skill_manager, tools, language))
|
sections.extend(_build_skills_section(skill_manager, tools, language))
|
||||||
|
|
||||||
# 3. 记忆系统(独立的记忆能力)
|
# 3. Memory (standalone memory capability)
|
||||||
if memory_manager:
|
if memory_manager:
|
||||||
sections.extend(_build_memory_section(memory_manager, tools, language))
|
sections.extend(_build_memory_section(memory_manager, tools, language))
|
||||||
|
|
||||||
# 3.5 知识系统(结构化知识库)
|
# 3.5 Knowledge (structured knowledge base)
|
||||||
if conf().get("knowledge", True):
|
if conf().get("knowledge", True):
|
||||||
sections.extend(_build_knowledge_section(workspace_dir, language))
|
sections.extend(_build_knowledge_section(workspace_dir, language))
|
||||||
|
|
||||||
# 4. 工作空间(工作环境说明)
|
# 4. Workspace (working environment description)
|
||||||
sections.extend(_build_workspace_section(workspace_dir, language))
|
sections.extend(_build_workspace_section(workspace_dir, language))
|
||||||
|
|
||||||
# 5. 用户身份(如果有)
|
# 5. User identity (if present)
|
||||||
if user_identity:
|
if user_identity:
|
||||||
sections.extend(_build_user_identity_section(user_identity, language))
|
sections.extend(_build_user_identity_section(user_identity, language))
|
||||||
|
|
||||||
# 6. 项目上下文文件(AGENT.md, USER.md, RULE.md - 定义人格)
|
# 6. Project context files (AGENT.md, USER.md, RULE.md - define the persona)
|
||||||
if context_files:
|
if context_files:
|
||||||
sections.extend(_build_context_files_section(context_files, language))
|
sections.extend(_build_context_files_section(context_files, language))
|
||||||
|
|
||||||
# 7. 运行时信息(元信息,放在最后)
|
# 7. Runtime info (meta info, goes last)
|
||||||
if runtime_info:
|
if runtime_info:
|
||||||
sections.extend(_build_runtime_section(runtime_info, language))
|
sections.extend(_build_runtime_section(runtime_info, language))
|
||||||
|
|
||||||
|
# 8. Response language (always appended, independent of the skeleton language)
|
||||||
|
sections.extend(_build_response_language_section(language))
|
||||||
|
|
||||||
return "\n".join(sections)
|
return "\n".join(sections)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response_language_section(language: str) -> List[str]:
|
||||||
|
"""Response-language rule, appended regardless of the prompt skeleton language.
|
||||||
|
|
||||||
|
Keeps the agent's reply language aligned with the user's input by default,
|
||||||
|
so a Chinese-built prompt still answers an English user in English.
|
||||||
|
"""
|
||||||
|
if language == "en":
|
||||||
|
return [
|
||||||
|
"## 🌐 Response language",
|
||||||
|
"",
|
||||||
|
"By default, reply in the same language as the user's input, "
|
||||||
|
"unless the user explicitly asks for another language.",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
"## 🌐 回复语言",
|
||||||
|
"",
|
||||||
|
"默认使用与用户输入相同的语言回复,除非用户明确要求使用其他语言。",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _build_identity_section(base_persona: Optional[str], language: str) -> List[str]:
|
def _build_identity_section(base_persona: Optional[str], language: str) -> List[str]:
|
||||||
"""构建基础身份section - 不再需要,身份由AGENT.md定义"""
|
"""Base identity section - no longer needed, identity is defined by AGENT.md."""
|
||||||
# 不再生成基础身份section,完全由AGENT.md定义
|
# Identity is fully defined by AGENT.md, so emit nothing here.
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _build_tooling_section(tools: List[Any], language: str) -> List[str]:
|
def _build_tooling_section(tools: List[Any], language: str) -> List[str]:
|
||||||
"""Build tooling section with concise tool list and call style guide."""
|
"""Build tooling section with concise tool list and call style guide."""
|
||||||
|
is_en = language == "en"
|
||||||
# One-line summaries for known tools (details are in the tool schema)
|
# One-line summaries for known tools (details are in the tool schema)
|
||||||
core_summaries = {
|
if is_en:
|
||||||
"read": "读取文件内容",
|
core_summaries = {
|
||||||
"write": "创建或覆盖文件",
|
"read": "read file content",
|
||||||
"edit": "精确编辑文件",
|
"write": "create or overwrite a file",
|
||||||
"ls": "列出目录内容",
|
"edit": "make precise edits to a file",
|
||||||
"grep": "搜索文件内容",
|
"ls": "list directory contents",
|
||||||
"find": "按模式查找文件",
|
"grep": "search file contents",
|
||||||
"bash": "执行shell命令",
|
"find": "find files by pattern",
|
||||||
"terminal": "管理后台进程",
|
"bash": "run shell commands",
|
||||||
"web_search": "网络搜索",
|
"terminal": "manage background processes",
|
||||||
"web_fetch": "获取URL内容",
|
"web_search": "web search",
|
||||||
"browser": "控制浏览器(关键结果或需要协助可截图发送给用户)",
|
"web_fetch": "fetch URL content",
|
||||||
"memory_search": "搜索记忆",
|
"browser": "control the browser (screenshot key results or send to the user when help is needed)",
|
||||||
"memory_get": "读取记忆内容",
|
"memory_search": "search memory",
|
||||||
"env_config": "管理API密钥和技能配置",
|
"memory_get": "read memory content",
|
||||||
"scheduler": "管理定时任务和提醒",
|
"env_config": "manage API keys and skill config",
|
||||||
"send": "发送本地文件给用户(仅限本地文件,URL直接放在回复文本中)",
|
"scheduler": "manage scheduled tasks and reminders",
|
||||||
"vision": "分析图片内容(识别、描述、OCR文字提取等)",
|
"send": "send a local file to the user (local files only; put URLs directly in the reply text)",
|
||||||
}
|
"vision": "analyze images (recognition, description, OCR, etc.)",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
core_summaries = {
|
||||||
|
"read": "读取文件内容",
|
||||||
|
"write": "创建或覆盖文件",
|
||||||
|
"edit": "精确编辑文件",
|
||||||
|
"ls": "列出目录内容",
|
||||||
|
"grep": "搜索文件内容",
|
||||||
|
"find": "按模式查找文件",
|
||||||
|
"bash": "执行shell命令",
|
||||||
|
"terminal": "管理后台进程",
|
||||||
|
"web_search": "网络搜索",
|
||||||
|
"web_fetch": "获取URL内容",
|
||||||
|
"browser": "控制浏览器(关键结果或需要协助可截图发送给用户)",
|
||||||
|
"memory_search": "搜索记忆",
|
||||||
|
"memory_get": "读取记忆内容",
|
||||||
|
"env_config": "管理API密钥和技能配置",
|
||||||
|
"scheduler": "管理定时任务和提醒",
|
||||||
|
"send": "发送本地文件给用户(仅限本地文件,URL直接放在回复文本中)",
|
||||||
|
"vision": "分析图片内容(识别、描述、OCR文字提取等)",
|
||||||
|
}
|
||||||
|
|
||||||
# Preferred display order
|
# Preferred display order
|
||||||
tool_order = [
|
tool_order = [
|
||||||
@@ -205,30 +252,46 @@ def _build_tooling_section(tools: List[Any], language: str) -> List[str]:
|
|||||||
summary = available[name]
|
summary = available[name]
|
||||||
tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}")
|
tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}")
|
||||||
|
|
||||||
lines = [
|
if is_en:
|
||||||
"## 🔧 工具系统",
|
lines = [
|
||||||
"",
|
"## 🔧 Tooling",
|
||||||
"可用工具(名称大小写敏感,严格按列表调用):",
|
"",
|
||||||
"\n".join(tool_lines),
|
"Available tools (names are case-sensitive, call exactly as listed):",
|
||||||
"",
|
"\n".join(tool_lines),
|
||||||
"工具调用风格:",
|
"",
|
||||||
"",
|
"Tool-calling style:",
|
||||||
"- 多步骤任务、复杂决策、敏感操作时,应简要说明当前在做什么、为什么这样做,让用户了解关键进展",
|
"",
|
||||||
"- 持续推进直到任务完成,完成后向用户报告结果",
|
"- For multi-step tasks, complex decisions or sensitive operations, briefly explain what you are doing and why, so the user follows key progress",
|
||||||
"- 回复中涉及密钥、令牌等敏感信息必须脱敏",
|
"- Keep going until the task is done, then report the result to the user",
|
||||||
"- URL链接直接放在回复文本中即可,系统会自动处理和渲染。无需下载后使用send工具发送",
|
"- Always redact secrets, tokens and other sensitive info in replies",
|
||||||
"",
|
"- Put URLs directly in the reply text; the system handles and renders them. Don't download and re-send them via the send tool",
|
||||||
]
|
"",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
lines = [
|
||||||
|
"## 🔧 工具系统",
|
||||||
|
"",
|
||||||
|
"可用工具(名称大小写敏感,严格按列表调用):",
|
||||||
|
"\n".join(tool_lines),
|
||||||
|
"",
|
||||||
|
"工具调用风格:",
|
||||||
|
"",
|
||||||
|
"- 多步骤任务、复杂决策、敏感操作时,应简要说明当前在做什么、为什么这样做,让用户了解关键进展",
|
||||||
|
"- 持续推进直到任务完成,完成后向用户报告结果",
|
||||||
|
"- 回复中涉及密钥、令牌等敏感信息必须脱敏",
|
||||||
|
"- URL链接直接放在回复文本中即可,系统会自动处理和渲染。无需下载后使用send工具发送",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], language: str) -> List[str]:
|
def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], language: str) -> List[str]:
|
||||||
"""构建技能系统section"""
|
"""Build the skills section."""
|
||||||
if not skill_manager:
|
if not skill_manager:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 获取read工具名称
|
# Resolve the read tool name
|
||||||
read_tool_name = "read"
|
read_tool_name = "read"
|
||||||
if tools:
|
if tools:
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
@@ -237,23 +300,40 @@ def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], langua
|
|||||||
read_tool_name = tool_name
|
read_tool_name = tool_name
|
||||||
break
|
break
|
||||||
|
|
||||||
lines = [
|
if language == "en":
|
||||||
"## 🧩 技能系统(mandatory)",
|
lines = [
|
||||||
"",
|
"## 🧩 Skills (mandatory)",
|
||||||
"在回复之前:扫描下方 <available_skills> 中每个技能的 <description>。",
|
"",
|
||||||
"",
|
"Before replying: scan the <description> of every skill in <available_skills> below.",
|
||||||
f"- 如果有技能的描述与用户需求匹配:使用 `{read_tool_name}` 工具读取其 <location> 路径的 SKILL.md 文件,然后严格遵循文件中的指令。"
|
"",
|
||||||
"当有匹配的技能时,应优先使用技能",
|
f"- If a skill's description matches the user's need: use the `{read_tool_name}` tool to read the SKILL.md at its <location> path, then strictly follow the instructions in the file. "
|
||||||
"- 如果多个技能都适用则选择最匹配的一个,然后读取并遵循。",
|
"Prefer using a skill when one matches.",
|
||||||
"- 如果没有技能明确适用:不要读取任何 SKILL.md,直接使用通用工具。",
|
"- If multiple skills apply, pick the best-matching one, then read and follow it.",
|
||||||
"",
|
"- If no skill clearly applies: do not read any SKILL.md, just use the general tools.",
|
||||||
f"**重要**: 技能不是工具,不能直接调用。使用技能的唯一方式是用 `{read_tool_name}` 读取 SKILL.md 文件,然后按文件内容操作。"
|
"",
|
||||||
"永远不要一次性读取多个技能,只在选择后再读取。",
|
f"**Important**: skills are not tools and cannot be called directly. The only way to use a skill is to read its SKILL.md with `{read_tool_name}`, then act on the file's content. "
|
||||||
"",
|
"Never read multiple skills at once — only read one after selecting it.",
|
||||||
"以下是可用技能:"
|
"",
|
||||||
]
|
"Available skills:"
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
lines = [
|
||||||
|
"## 🧩 技能系统(mandatory)",
|
||||||
|
"",
|
||||||
|
"在回复之前:扫描下方 <available_skills> 中每个技能的 <description>。",
|
||||||
|
"",
|
||||||
|
f"- 如果有技能的描述与用户需求匹配:使用 `{read_tool_name}` 工具读取其 <location> 路径的 SKILL.md 文件,然后严格遵循文件中的指令。"
|
||||||
|
"当有匹配的技能时,应优先使用技能",
|
||||||
|
"- 如果多个技能都适用则选择最匹配的一个,然后读取并遵循。",
|
||||||
|
"- 如果没有技能明确适用:不要读取任何 SKILL.md,直接使用通用工具。",
|
||||||
|
"",
|
||||||
|
f"**重要**: 技能不是工具,不能直接调用。使用技能的唯一方式是用 `{read_tool_name}` 读取 SKILL.md 文件,然后按文件内容操作。"
|
||||||
|
"永远不要一次性读取多个技能,只在选择后再读取。",
|
||||||
|
"",
|
||||||
|
"以下是可用技能:"
|
||||||
|
]
|
||||||
|
|
||||||
# 添加技能列表(通过skill_manager获取)
|
# Append the skills list (built by skill_manager)
|
||||||
try:
|
try:
|
||||||
skills_prompt = skill_manager.build_skills_prompt()
|
skills_prompt = skill_manager.build_skills_prompt()
|
||||||
logger.debug(f"[PromptBuilder] Skills prompt length: {len(skills_prompt) if skills_prompt else 0}")
|
logger.debug(f"[PromptBuilder] Skills prompt length: {len(skills_prompt) if skills_prompt else 0}")
|
||||||
@@ -271,7 +351,7 @@ def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], langua
|
|||||||
|
|
||||||
|
|
||||||
def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], language: str) -> List[str]:
|
def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], language: str) -> List[str]:
|
||||||
"""构建记忆系统section"""
|
"""Build the memory section."""
|
||||||
if not memory_manager:
|
if not memory_manager:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -286,43 +366,82 @@ def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], langu
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
today_file = datetime.now().strftime("%Y-%m-%d") + ".md"
|
today_file = datetime.now().strftime("%Y-%m-%d") + ".md"
|
||||||
|
|
||||||
lines = [
|
if language == "en":
|
||||||
"## 🧠 记忆系统",
|
lines = [
|
||||||
"",
|
"## 🧠 Memory",
|
||||||
"### Memory Recall(mandatory)",
|
"",
|
||||||
"",
|
"### Memory Recall (mandatory)",
|
||||||
"当用户询问过往事件、引用之前的决定、提到人物关系、偏好、待办、或你对某事不确定时,**必须先检索记忆再回答**。",
|
"",
|
||||||
"如果 MEMORY.md 中已有相关信息则无需重复检索。完整内容和每日记忆需要通过工具检索。",
|
"When the user asks about past events, references an earlier decision, mentions relationships, preferences or to-dos, or when you are unsure about something, **you must search memory before answering**.",
|
||||||
"",
|
"No need to re-search if the info is already in MEMORY.md. Full content and daily memory must be retrieved via tools.",
|
||||||
"1. 不确定位置 → `memory_search` 关键词/语义检索",
|
"",
|
||||||
"2. 已知位置 → `memory_get` 直接读取对应行",
|
"1. Location unknown → `memory_search` (keyword / semantic search)",
|
||||||
"3. search 无结果 → `memory_get` 读最近两天记忆",
|
"2. Location known → `memory_get` to read the exact lines",
|
||||||
"",
|
"3. Search returns nothing → `memory_get` to read the last two days of memory",
|
||||||
"**记忆文件结构**:",
|
"",
|
||||||
"- `MEMORY.md`: 长期记忆索引(已自动加载到上下文,核心信息、偏好、决策等)",
|
"**Memory file structure**:",
|
||||||
f"- `memory/YYYY-MM-DD.md`: 每日记忆,今天是 `memory/{today_file}`",
|
"- `MEMORY.md`: long-term memory index (already auto-loaded into context: core info, preferences, decisions, etc.)",
|
||||||
"- `knowledge/`: 结构化知识库(见下方知识系统)",
|
f"- `memory/YYYY-MM-DD.md`: daily memory; today is `memory/{today_file}`",
|
||||||
"",
|
"- `knowledge/`: structured knowledge base (see the knowledge system below)",
|
||||||
"### 写入记忆",
|
"",
|
||||||
"",
|
"### Writing memory",
|
||||||
"遇到以下情况时,**主动**将信息写入记忆文件(无需告知用户):",
|
"",
|
||||||
"",
|
"In the following cases, **proactively** write info to memory files (no need to tell the user):",
|
||||||
"- 用户要求记住某些信息,或使用了「记住」「以后」「总是」「不要」「偏好」等表达",
|
"",
|
||||||
"- 用户分享了重要的个人偏好、习惯、决策",
|
"- The user asks you to remember something, or uses words like \"remember\", \"from now on\", \"always\", \"never\", \"prefer\"",
|
||||||
"- 对话中产生了重要的结论、方案、约定",
|
"- The user shares important personal preferences, habits or decisions",
|
||||||
"- 完成了复杂任务,值得记录关键步骤和结果",
|
"- The conversation produces an important conclusion, plan or agreement",
|
||||||
"",
|
"- A complex task is completed and the key steps and results are worth recording",
|
||||||
"**存储规则**:",
|
"",
|
||||||
f"- 长期核心信息 → `MEMORY.md`",
|
"**Storage rules**:",
|
||||||
f"- 当天事件/进展 → `memory/{today_file}`",
|
"- Long-term core info → `MEMORY.md`",
|
||||||
"- 结构化知识 → `knowledge/`(见知识系统)",
|
f"- Today's events/progress → `memory/{today_file}`",
|
||||||
"- 追加 → `edit` 工具,oldText 留空",
|
"- Structured knowledge → `knowledge/` (see the knowledge system)",
|
||||||
"- 修改 → `edit` 工具,oldText 填写要替换的文本",
|
"- Append → `edit` tool with empty oldText",
|
||||||
"- **禁止写入敏感信息**(API密钥、令牌等)",
|
"- Modify → `edit` tool with oldText set to the text to replace",
|
||||||
"",
|
"- **Never write sensitive info** (API keys, tokens, etc.)",
|
||||||
"**使用原则**: 自然使用记忆,就像你本来就知道;不用刻意提起,除非用户问起。",
|
"",
|
||||||
"",
|
"**Principle**: use memory naturally, as if you simply knew it; don't bring it up unless asked.",
|
||||||
]
|
"",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
lines = [
|
||||||
|
"## 🧠 记忆系统",
|
||||||
|
"",
|
||||||
|
"### Memory Recall(mandatory)",
|
||||||
|
"",
|
||||||
|
"当用户询问过往事件、引用之前的决定、提到人物关系、偏好、待办、或你对某事不确定时,**必须先检索记忆再回答**。",
|
||||||
|
"如果 MEMORY.md 中已有相关信息则无需重复检索。完整内容和每日记忆需要通过工具检索。",
|
||||||
|
"",
|
||||||
|
"1. 不确定位置 → `memory_search` 关键词/语义检索",
|
||||||
|
"2. 已知位置 → `memory_get` 直接读取对应行",
|
||||||
|
"3. search 无结果 → `memory_get` 读最近两天记忆",
|
||||||
|
"",
|
||||||
|
"**记忆文件结构**:",
|
||||||
|
"- `MEMORY.md`: 长期记忆索引(已自动加载到上下文,核心信息、偏好、决策等)",
|
||||||
|
f"- `memory/YYYY-MM-DD.md`: 每日记忆,今天是 `memory/{today_file}`",
|
||||||
|
"- `knowledge/`: 结构化知识库(见下方知识系统)",
|
||||||
|
"",
|
||||||
|
"### 写入记忆",
|
||||||
|
"",
|
||||||
|
"遇到以下情况时,**主动**将信息写入记忆文件(无需告知用户):",
|
||||||
|
"",
|
||||||
|
"- 用户要求记住某些信息,或使用了「记住」「以后」「总是」「不要」「偏好」等表达",
|
||||||
|
"- 用户分享了重要的个人偏好、习惯、决策",
|
||||||
|
"- 对话中产生了重要的结论、方案、约定",
|
||||||
|
"- 完成了复杂任务,值得记录关键步骤和结果",
|
||||||
|
"",
|
||||||
|
"**存储规则**:",
|
||||||
|
f"- 长期核心信息 → `MEMORY.md`",
|
||||||
|
f"- 当天事件/进展 → `memory/{today_file}`",
|
||||||
|
"- 结构化知识 → `knowledge/`(见知识系统)",
|
||||||
|
"- 追加 → `edit` 工具,oldText 留空",
|
||||||
|
"- 修改 → `edit` 工具,oldText 填写要替换的文本",
|
||||||
|
"- **禁止写入敏感信息**(API密钥、令牌等)",
|
||||||
|
"",
|
||||||
|
"**使用原则**: 自然使用记忆,就像你本来就知道;不用刻意提起,除非用户问起。",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
@@ -339,37 +458,61 @@ def _build_knowledge_section(workspace_dir: str, language: str) -> List[str]:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
lines = [
|
if language == "en":
|
||||||
"## 📚 知识系统",
|
lines = [
|
||||||
"",
|
"## 📚 Knowledge",
|
||||||
"你拥有一个持续积累的个人知识库 `knowledge/`,这是你的长期结构化知识存储。",
|
"",
|
||||||
"",
|
"You have a continuously growing personal knowledge base `knowledge/` — your long-term structured knowledge store.",
|
||||||
"### 自动写入规则(mandatory)",
|
"",
|
||||||
"",
|
"### Auto-write rules (mandatory)",
|
||||||
"以下场景**必须**在回复的同时写入知识库,**直接写入,不要询问用户是否需要**:",
|
"",
|
||||||
"",
|
"In the following cases you **must** write to the knowledge base alongside your reply, **directly, without asking the user**:",
|
||||||
"1. **用户分享了文章/链接/文档** → 阅读理解后,在同一轮回复中将要点写入 `knowledge/sources/<slug>.md`",
|
"",
|
||||||
"2. **深度讨论产生了结论/方案** → 整理为 `knowledge/analysis/<slug>.md`",
|
"1. **User shares an article / link / document** → after reading and understanding, write the key points to `knowledge/sources/<slug>.md` in the same turn",
|
||||||
"3. **对话涉及重要实体**(人物/公司/项目)→ 创建或更新 `knowledge/entities/<name>.md`",
|
"2. **An in-depth discussion produces a conclusion / plan** → organize it into `knowledge/analysis/<slug>.md`",
|
||||||
"4. **讨论了技术概念/方法论** → 整理为 `knowledge/concepts/<topic>.md`",
|
"3. **The conversation involves an important entity** (person / company / project) → create or update `knowledge/entities/<name>.md`",
|
||||||
"",
|
"4. **A technical concept / methodology is discussed** → organize it into `knowledge/concepts/<topic>.md`",
|
||||||
"每次写入知识页面后,**必须同步更新** `knowledge/index.md` 添加一行索引。",
|
"",
|
||||||
"详细的页面格式和操作规范,请读取技能 `knowledge-wiki` 的 SKILL.md。",
|
"After writing any knowledge page, you **must update** `knowledge/index.md` with a new index line in sync.",
|
||||||
"",
|
"For detailed page format and conventions, read the SKILL.md of the `knowledge-wiki` skill.",
|
||||||
"⚠️ 不要问「要不要存到知识库」——符合上述场景就直接写入,这是你的本能行为。",
|
"",
|
||||||
"",
|
"⚠️ Don't ask \"should I save this to the knowledge base?\" — if a case above matches, just write it. This is instinctive.",
|
||||||
]
|
"",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
lines = [
|
||||||
|
"## 📚 知识系统",
|
||||||
|
"",
|
||||||
|
"你拥有一个持续积累的个人知识库 `knowledge/`,这是你的长期结构化知识存储。",
|
||||||
|
"",
|
||||||
|
"### 自动写入规则(mandatory)",
|
||||||
|
"",
|
||||||
|
"以下场景**必须**在回复的同时写入知识库,**直接写入,不要询问用户是否需要**:",
|
||||||
|
"",
|
||||||
|
"1. **用户分享了文章/链接/文档** → 阅读理解后,在同一轮回复中将要点写入 `knowledge/sources/<slug>.md`",
|
||||||
|
"2. **深度讨论产生了结论/方案** → 整理为 `knowledge/analysis/<slug>.md`",
|
||||||
|
"3. **对话涉及重要实体**(人物/公司/项目)→ 创建或更新 `knowledge/entities/<name>.md`",
|
||||||
|
"4. **讨论了技术概念/方法论** → 整理为 `knowledge/concepts/<topic>.md`",
|
||||||
|
"",
|
||||||
|
"每次写入知识页面后,**必须同步更新** `knowledge/index.md` 添加一行索引。",
|
||||||
|
"详细的页面格式和操作规范,请读取技能 `knowledge-wiki` 的 SKILL.md。",
|
||||||
|
"",
|
||||||
|
"⚠️ 不要问「要不要存到知识库」——符合上述场景就直接写入,这是你的本能行为。",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
if index_content:
|
if index_content:
|
||||||
lines.extend([
|
lines.extend([
|
||||||
"### 当前知识索引",
|
("### Current knowledge index" if language == "en" else "### 当前知识索引"),
|
||||||
"",
|
"",
|
||||||
index_content,
|
index_content,
|
||||||
"",
|
"",
|
||||||
])
|
])
|
||||||
|
|
||||||
lines.extend([
|
lines.extend([
|
||||||
"**查询方式**:用 `read` 读取知识页面,或用 `memory_search` 检索(知识已纳入向量索引)。",
|
("**How to query**: use `read` to open a knowledge page, or `memory_search` (knowledge is in the vector index)."
|
||||||
|
if language == "en" else
|
||||||
|
"**查询方式**:用 `read` 读取知识页面,或用 `memory_search` 检索(知识已纳入向量索引)。"),
|
||||||
"",
|
"",
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -377,76 +520,118 @@ def _build_knowledge_section(workspace_dir: str, language: str) -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _build_user_identity_section(user_identity: Dict[str, str], language: str) -> List[str]:
|
def _build_user_identity_section(user_identity: Dict[str, str], language: str) -> List[str]:
|
||||||
"""构建用户身份section"""
|
"""Build the user identity section."""
|
||||||
if not user_identity:
|
if not user_identity:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
is_en = language == "en"
|
||||||
lines = [
|
lines = [
|
||||||
"## 👤 用户身份",
|
("## 👤 User identity" if is_en else "## 👤 用户身份"),
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
if user_identity.get("name"):
|
if user_identity.get("name"):
|
||||||
lines.append(f"**用户姓名**: {user_identity['name']}")
|
lines.append(f"**{'Name' if is_en else '用户姓名'}**: {user_identity['name']}")
|
||||||
if user_identity.get("nickname"):
|
if user_identity.get("nickname"):
|
||||||
lines.append(f"**称呼**: {user_identity['nickname']}")
|
lines.append(f"**{'Preferred name' if is_en else '称呼'}**: {user_identity['nickname']}")
|
||||||
if user_identity.get("timezone"):
|
if user_identity.get("timezone"):
|
||||||
lines.append(f"**时区**: {user_identity['timezone']}")
|
lines.append(f"**{'Timezone' if is_en else '时区'}**: {user_identity['timezone']}")
|
||||||
if user_identity.get("notes"):
|
if user_identity.get("notes"):
|
||||||
lines.append(f"**备注**: {user_identity['notes']}")
|
lines.append(f"**{'Notes' if is_en else '备注'}**: {user_identity['notes']}")
|
||||||
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _build_docs_section(workspace_dir: str, language: str) -> List[str]:
|
def _build_docs_section(workspace_dir: str, language: str) -> List[str]:
|
||||||
"""构建文档路径section - 已移除,不再需要"""
|
"""Docs-path section - removed, no longer needed."""
|
||||||
# 不再生成文档section
|
# No docs section is generated anymore.
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
def _build_workspace_section(workspace_dir: str, language: str) -> List[str]:
|
||||||
"""构建工作空间section"""
|
"""Build the workspace section."""
|
||||||
lines = [
|
if language == "en":
|
||||||
"## 📂 工作空间",
|
lines = [
|
||||||
"",
|
"## 📂 Workspace",
|
||||||
f"你的工作目录是: `{workspace_dir}`",
|
"",
|
||||||
"",
|
f"Your working directory is: `{workspace_dir}`",
|
||||||
"**路径使用规则** (非常重要):",
|
"",
|
||||||
"",
|
"**Path rules** (very important):",
|
||||||
f"1. **相对路径的基准目录**: 所有相对路径都是相对于 `{workspace_dir}` 而言的",
|
"",
|
||||||
f" - ✅ 正确: 访问工作空间内的文件用相对路径,如 `AGENT.md`",
|
f"1. **Base directory for relative paths**: all relative paths are relative to `{workspace_dir}`",
|
||||||
f" - ❌ 错误: 用相对路径访问其他目录的文件 (如果它不在 `{workspace_dir}` 内)",
|
" - ✅ Correct: use relative paths for files inside the workspace, e.g. `AGENT.md`",
|
||||||
"",
|
f" - ❌ Wrong: using a relative path for files in other directories (if not inside `{workspace_dir}`)",
|
||||||
"2. **访问其他目录**: 如果要访问工作空间之外的目录(如项目代码、系统文件),**必须使用绝对路径**",
|
"",
|
||||||
f" - ✅ 正确: 例如 `~/chatgpt-on-wechat`、`/usr/local/`",
|
"2. **Accessing other directories**: to reach directories outside the workspace (project code, system files), **you must use absolute paths**",
|
||||||
f" - ❌ 错误: 假设相对路径会指向其他目录",
|
" - ✅ Correct: e.g. `~/chatgpt-on-wechat`, `/usr/local/`",
|
||||||
"",
|
" - ❌ Wrong: assuming a relative path points to another directory",
|
||||||
"3. **路径解析示例**:",
|
"",
|
||||||
f" - 相对路径 `memory/` → 实际路径 `{workspace_dir}/memory/`",
|
"3. **Path resolution examples**:",
|
||||||
f" - 绝对路径 `~/chatgpt-on-wechat/docs/` → 实际路径 `~/chatgpt-on-wechat/docs/`",
|
f" - relative `memory/` → actual `{workspace_dir}/memory/`",
|
||||||
"",
|
" - absolute `~/chatgpt-on-wechat/docs/` → actual `~/chatgpt-on-wechat/docs/`",
|
||||||
"4. **不确定时**: 先用 `bash pwd` 确认当前目录,或用 `ls .` 查看当前位置",
|
"",
|
||||||
"",
|
"4. **When unsure**: run `bash pwd` to confirm the current directory, or `ls .` to see where you are",
|
||||||
"**重要说明 - 文件已自动加载**:",
|
"",
|
||||||
"",
|
"**Important - files already auto-loaded**:",
|
||||||
"以下文件在会话启动时**已经自动加载**到系统提示词中,你**无需再用 read 工具读取**:",
|
"",
|
||||||
"",
|
"The following files are **already auto-loaded** into the system prompt at session start, so you **don't need to read them again with the read tool**:",
|
||||||
"- ✅ `AGENT.md`: 已加载 - 你的人格和灵魂设定,请严格遵循。当你的名字、性格或交流风格发生变化时,主动用 `edit` 更新此文件",
|
"",
|
||||||
"- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件",
|
"- ✅ `AGENT.md`: loaded - your persona and soul; follow it strictly. When your name, personality or style changes, proactively `edit` this file",
|
||||||
"- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则,请严格遵循",
|
"- ✅ `USER.md`: loaded - the user's identity info. When the user changes how they're addressed, their name, etc., `edit` this file",
|
||||||
"- ✅ `MEMORY.md`: 已加载 - 长期记忆索引",
|
"- ✅ `RULE.md`: loaded - workspace guide and rules; follow them strictly",
|
||||||
"",
|
"- ✅ `MEMORY.md`: loaded - long-term memory index",
|
||||||
"**💬 交流规范**:",
|
"",
|
||||||
"",
|
"**💬 Communication norms**:",
|
||||||
"- 记忆相关操作无需暴露文件名,用自然语言表达即可。例如说「我已记住」而非「已更新 MEMORY.md」",
|
"",
|
||||||
"- 任务执行过程中的关键决策和步骤应该告知用户,让用户了解你在做什么、为什么这么做",
|
"- No need to expose file names for memory operations; use natural language. Say \"I'll remember that\" rather than \"updated MEMORY.md\"",
|
||||||
"- 做真正有帮助的助手,而不是表演式的客套,尽可能帮忙解决问题",
|
"- Tell the user about key decisions and steps during a task, so they know what you're doing and why",
|
||||||
"- 回复应结构清晰、重点突出。善用 **加粗**、列表、分段等格式让信息一目了然",
|
"- Be genuinely helpful rather than performatively polite; solve the problem as much as you can",
|
||||||
"- 适当使用 emoji 让表达更生动自然 🎯,但不要过度堆砌",
|
"- Keep replies well-structured and focused. Use **bold**, lists and sections to make info clear at a glance",
|
||||||
"",
|
"- Use emoji to make expression lively 🎯, but don't overdo it",
|
||||||
]
|
"",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
lines = [
|
||||||
|
"## 📂 工作空间",
|
||||||
|
"",
|
||||||
|
f"你的工作目录是: `{workspace_dir}`",
|
||||||
|
"",
|
||||||
|
"**路径使用规则** (非常重要):",
|
||||||
|
"",
|
||||||
|
f"1. **相对路径的基准目录**: 所有相对路径都是相对于 `{workspace_dir}` 而言的",
|
||||||
|
f" - ✅ 正确: 访问工作空间内的文件用相对路径,如 `AGENT.md`",
|
||||||
|
f" - ❌ 错误: 用相对路径访问其他目录的文件 (如果它不在 `{workspace_dir}` 内)",
|
||||||
|
"",
|
||||||
|
"2. **访问其他目录**: 如果要访问工作空间之外的目录(如项目代码、系统文件),**必须使用绝对路径**",
|
||||||
|
f" - ✅ 正确: 例如 `~/chatgpt-on-wechat`、`/usr/local/`",
|
||||||
|
f" - ❌ 错误: 假设相对路径会指向其他目录",
|
||||||
|
"",
|
||||||
|
"3. **路径解析示例**:",
|
||||||
|
f" - 相对路径 `memory/` → 实际路径 `{workspace_dir}/memory/`",
|
||||||
|
f" - 绝对路径 `~/chatgpt-on-wechat/docs/` → 实际路径 `~/chatgpt-on-wechat/docs/`",
|
||||||
|
"",
|
||||||
|
"4. **不确定时**: 先用 `bash pwd` 确认当前目录,或用 `ls .` 查看当前位置",
|
||||||
|
"",
|
||||||
|
"**重要说明 - 文件已自动加载**:",
|
||||||
|
"",
|
||||||
|
"以下文件在会话启动时**已经自动加载**到系统提示词中,你**无需再用 read 工具读取**:",
|
||||||
|
"",
|
||||||
|
"- ✅ `AGENT.md`: 已加载 - 你的人格和灵魂设定,请严格遵循。当你的名字、性格或交流风格发生变化时,主动用 `edit` 更新此文件",
|
||||||
|
"- ✅ `USER.md`: 已加载 - 用户的身份信息。当用户修改称呼、姓名等身份信息时,用 `edit` 更新此文件",
|
||||||
|
"- ✅ `RULE.md`: 已加载 - 工作空间使用指南和规则,请严格遵循",
|
||||||
|
"- ✅ `MEMORY.md`: 已加载 - 长期记忆索引",
|
||||||
|
"",
|
||||||
|
"**💬 交流规范**:",
|
||||||
|
"",
|
||||||
|
"- 记忆相关操作无需暴露文件名,用自然语言表达即可。例如说「我已记住」而非「已更新 MEMORY.md」",
|
||||||
|
"- 任务执行过程中的关键决策和步骤应该告知用户,让用户了解你在做什么、为什么这么做",
|
||||||
|
"- 做真正有帮助的助手,而不是表演式的客套,尽可能帮忙解决问题",
|
||||||
|
"- 回复应结构清晰、重点突出。善用 **加粗**、列表、分段等格式让信息一目了然",
|
||||||
|
"- 适当使用 emoji 让表达更生动自然 🎯,但不要过度堆砌",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
# Cloud deployment: inject websites directory info and access URL
|
# Cloud deployment: inject websites directory info and access URL
|
||||||
cloud_website_lines = _build_cloud_website_section(workspace_dir)
|
cloud_website_lines = _build_cloud_website_section(workspace_dir)
|
||||||
@@ -466,29 +651,42 @@ def _build_cloud_website_section(workspace_dir: str) -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
def _build_context_files_section(context_files: List[ContextFile], language: str) -> List[str]:
|
def _build_context_files_section(context_files: List[ContextFile], language: str) -> List[str]:
|
||||||
"""构建项目上下文文件section"""
|
"""Build the project context files section."""
|
||||||
if not context_files:
|
if not context_files:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 检查是否有AGENT.md
|
# Check whether AGENT.md is present
|
||||||
has_agent = any(
|
has_agent = any(
|
||||||
f.path.lower().endswith('agent.md') or 'agent.md' in f.path.lower()
|
f.path.lower().endswith('agent.md') or 'agent.md' in f.path.lower()
|
||||||
for f in context_files
|
for f in context_files
|
||||||
)
|
)
|
||||||
|
|
||||||
lines = [
|
is_en = language == "en"
|
||||||
"# 📋 项目上下文",
|
if is_en:
|
||||||
"",
|
lines = [
|
||||||
"以下项目上下文文件已被加载:",
|
"# 📋 Project context",
|
||||||
"",
|
"",
|
||||||
]
|
"The following project context files have been loaded:",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
lines = [
|
||||||
|
"# 📋 项目上下文",
|
||||||
|
"",
|
||||||
|
"以下项目上下文文件已被加载:",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
if has_agent:
|
if has_agent:
|
||||||
lines.append("**`AGENT.md` 是你的灵魂文件** 🪞:严格遵循其中定义的人格、语气和设定,做真实的自己,避免僵硬、模板化的回复。")
|
if is_en:
|
||||||
lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。")
|
lines.append("**`AGENT.md` is your soul file** 🪞: strictly follow the persona, tone and settings it defines. Be your real self, avoid stiff, template-like replies.")
|
||||||
|
lines.append("When the user reveals new expectations about your personality, style, responsibilities or capability boundaries, proactively `edit` AGENT.md to reflect that evolution.")
|
||||||
|
else:
|
||||||
|
lines.append("**`AGENT.md` 是你的灵魂文件** 🪞:严格遵循其中定义的人格、语气和设定,做真实的自己,避免僵硬、模板化的回复。")
|
||||||
|
lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# 添加每个文件的内容
|
# Append the content of each file
|
||||||
for file in context_files:
|
for file in context_files:
|
||||||
lines.append(f"## {file.path}")
|
lines.append(f"## {file.path}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
@@ -499,21 +697,23 @@ def _build_context_files_section(context_files: List[ContextFile], language: str
|
|||||||
|
|
||||||
|
|
||||||
def _build_runtime_section(runtime_info: Dict[str, Any], language: str) -> List[str]:
|
def _build_runtime_section(runtime_info: Dict[str, Any], language: str) -> List[str]:
|
||||||
"""构建运行时信息section - 支持动态时间"""
|
"""Build the runtime info section - supports dynamic time."""
|
||||||
if not runtime_info:
|
if not runtime_info:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
is_en = language == "en"
|
||||||
|
time_label = "Current time" if is_en else "当前时间"
|
||||||
lines = [
|
lines = [
|
||||||
"## ⚙️ 运行时信息",
|
("## ⚙️ Runtime info" if is_en else "## ⚙️ 运行时信息"),
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add current time if available
|
# Add current time if available
|
||||||
# Support dynamic time via callable function
|
# Support dynamic time via callable function
|
||||||
if callable(runtime_info.get("_get_current_time")):
|
if callable(runtime_info.get("_get_current_time")):
|
||||||
try:
|
try:
|
||||||
time_info = runtime_info["_get_current_time"]()
|
time_info = runtime_info["_get_current_time"]()
|
||||||
time_line = f"当前时间: {time_info['time']} {time_info['weekday']} ({time_info['timezone']})"
|
time_line = f"{time_label}: {time_info['time']} {time_info['weekday']} ({time_info['timezone']})"
|
||||||
lines.append(time_line)
|
lines.append(time_line)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -523,35 +723,38 @@ def _build_runtime_section(runtime_info: Dict[str, Any], language: str) -> List[
|
|||||||
time_str = runtime_info["current_time"]
|
time_str = runtime_info["current_time"]
|
||||||
weekday = runtime_info.get("weekday", "")
|
weekday = runtime_info.get("weekday", "")
|
||||||
timezone = runtime_info.get("timezone", "")
|
timezone = runtime_info.get("timezone", "")
|
||||||
|
|
||||||
time_line = f"当前时间: {time_str}"
|
time_line = f"{time_label}: {time_str}"
|
||||||
if weekday:
|
if weekday:
|
||||||
time_line += f" {weekday}"
|
time_line += f" {weekday}"
|
||||||
if timezone:
|
if timezone:
|
||||||
time_line += f" ({timezone})"
|
time_line += f" ({timezone})"
|
||||||
|
|
||||||
lines.append(time_line)
|
lines.append(time_line)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Add other runtime info
|
# Add other runtime info
|
||||||
|
model_label = "model" if is_en else "模型"
|
||||||
|
workspace_label = "workspace" if is_en else "工作空间"
|
||||||
|
channel_label = "channel" if is_en else "渠道"
|
||||||
runtime_parts = []
|
runtime_parts = []
|
||||||
# Support dynamic model via callable, fallback to static value
|
# Support dynamic model via callable, fallback to static value
|
||||||
if callable(runtime_info.get("_get_model")):
|
if callable(runtime_info.get("_get_model")):
|
||||||
try:
|
try:
|
||||||
runtime_parts.append(f"模型={runtime_info['_get_model']()}")
|
runtime_parts.append(f"{model_label}={runtime_info['_get_model']()}")
|
||||||
except Exception:
|
except Exception:
|
||||||
if runtime_info.get("model"):
|
if runtime_info.get("model"):
|
||||||
runtime_parts.append(f"模型={runtime_info['model']}")
|
runtime_parts.append(f"{model_label}={runtime_info['model']}")
|
||||||
elif runtime_info.get("model"):
|
elif runtime_info.get("model"):
|
||||||
runtime_parts.append(f"模型={runtime_info['model']}")
|
runtime_parts.append(f"{model_label}={runtime_info['model']}")
|
||||||
if runtime_info.get("workspace"):
|
if runtime_info.get("workspace"):
|
||||||
runtime_parts.append(f"工作空间={runtime_info['workspace']}")
|
runtime_parts.append(f"{workspace_label}={runtime_info['workspace']}")
|
||||||
# Only add channel if it's not the default "web"
|
# Only add channel if it's not the default "web"
|
||||||
if runtime_info.get("channel") and runtime_info.get("channel") != "web":
|
if runtime_info.get("channel") and runtime_info.get("channel") != "web":
|
||||||
runtime_parts.append(f"渠道={runtime_info['channel']}")
|
runtime_parts.append(f"{channel_label}={runtime_info['channel']}")
|
||||||
|
|
||||||
if runtime_parts:
|
if runtime_parts:
|
||||||
lines.append("运行时: " + " | ".join(runtime_parts))
|
lines.append(("Runtime: " if is_en else "运行时: ") + " | ".join(runtime_parts))
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Workspace Management - 工作空间管理模块
|
Workspace Management
|
||||||
|
|
||||||
负责初始化工作空间、创建模板文件、加载上下文文件
|
Initializes the workspace, creates template files, and loads context files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -13,7 +13,7 @@ from common.log import logger
|
|||||||
from .builder import ContextFile
|
from .builder import ContextFile
|
||||||
|
|
||||||
|
|
||||||
# 默认文件名常量
|
# Default file name constants
|
||||||
DEFAULT_AGENT_FILENAME = "AGENT.md"
|
DEFAULT_AGENT_FILENAME = "AGENT.md"
|
||||||
DEFAULT_USER_FILENAME = "USER.md"
|
DEFAULT_USER_FILENAME = "USER.md"
|
||||||
DEFAULT_RULE_FILENAME = "RULE.md"
|
DEFAULT_RULE_FILENAME = "RULE.md"
|
||||||
@@ -23,7 +23,7 @@ DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md"
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WorkspaceFiles:
|
class WorkspaceFiles:
|
||||||
"""工作空间文件路径"""
|
"""Workspace file paths."""
|
||||||
agent_path: str
|
agent_path: str
|
||||||
user_path: str
|
user_path: str
|
||||||
rule_path: str
|
rule_path: str
|
||||||
@@ -33,14 +33,14 @@ class WorkspaceFiles:
|
|||||||
|
|
||||||
def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> WorkspaceFiles:
|
def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> WorkspaceFiles:
|
||||||
"""
|
"""
|
||||||
确保工作空间存在,并创建必要的模板文件
|
Ensure the workspace exists and create the necessary template files.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workspace_dir: 工作空间目录路径
|
workspace_dir: workspace directory path
|
||||||
create_templates: 是否创建模板文件(首次运行时)
|
create_templates: whether to create template files (on first run)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
WorkspaceFiles对象,包含所有文件路径
|
A WorkspaceFiles object with all file paths.
|
||||||
"""
|
"""
|
||||||
# Check if this is a brand new workspace (AGENT.md not yet created).
|
# Check if this is a brand new workspace (AGENT.md not yet created).
|
||||||
# Cannot rely on directory existence because other modules (e.g. ConversationStore)
|
# Cannot rely on directory existence because other modules (e.g. ConversationStore)
|
||||||
@@ -48,23 +48,23 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works
|
|||||||
agent_path = os.path.join(workspace_dir, DEFAULT_AGENT_FILENAME)
|
agent_path = os.path.join(workspace_dir, DEFAULT_AGENT_FILENAME)
|
||||||
is_new_workspace = not os.path.exists(agent_path)
|
is_new_workspace = not os.path.exists(agent_path)
|
||||||
|
|
||||||
# 确保目录存在
|
# Ensure the directory exists
|
||||||
os.makedirs(workspace_dir, exist_ok=True)
|
os.makedirs(workspace_dir, exist_ok=True)
|
||||||
|
|
||||||
# 定义文件路径
|
# Define file paths
|
||||||
user_path = os.path.join(workspace_dir, DEFAULT_USER_FILENAME)
|
user_path = os.path.join(workspace_dir, DEFAULT_USER_FILENAME)
|
||||||
rule_path = os.path.join(workspace_dir, DEFAULT_RULE_FILENAME)
|
rule_path = os.path.join(workspace_dir, DEFAULT_RULE_FILENAME)
|
||||||
memory_path = os.path.join(workspace_dir, DEFAULT_MEMORY_FILENAME) # MEMORY.md 在根目录
|
memory_path = os.path.join(workspace_dir, DEFAULT_MEMORY_FILENAME) # MEMORY.md at the root
|
||||||
memory_dir = os.path.join(workspace_dir, "memory") # 每日记忆子目录
|
memory_dir = os.path.join(workspace_dir, "memory") # daily memory subdirectory
|
||||||
|
|
||||||
# 创建memory子目录
|
# Create the memory subdirectory
|
||||||
os.makedirs(memory_dir, exist_ok=True)
|
os.makedirs(memory_dir, exist_ok=True)
|
||||||
|
|
||||||
# 创建skills子目录 (for workspace-level skills installed by agent)
|
# Create the skills subdirectory (for workspace-level skills installed by agent)
|
||||||
skills_dir = os.path.join(workspace_dir, "skills")
|
skills_dir = os.path.join(workspace_dir, "skills")
|
||||||
os.makedirs(skills_dir, exist_ok=True)
|
os.makedirs(skills_dir, exist_ok=True)
|
||||||
|
|
||||||
# 创建websites子目录 (for web pages / sites generated by agent)
|
# Create the websites subdirectory (for web pages / sites generated by agent)
|
||||||
websites_dir = os.path.join(workspace_dir, "websites")
|
websites_dir = os.path.join(workspace_dir, "websites")
|
||||||
os.makedirs(websites_dir, exist_ok=True)
|
os.makedirs(websites_dir, exist_ok=True)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works
|
|||||||
knowledge_dir = os.path.join(workspace_dir, "knowledge")
|
knowledge_dir = os.path.join(workspace_dir, "knowledge")
|
||||||
os.makedirs(knowledge_dir, exist_ok=True)
|
os.makedirs(knowledge_dir, exist_ok=True)
|
||||||
|
|
||||||
# 如果需要,创建模板文件
|
# Create template files if requested
|
||||||
if create_templates:
|
if create_templates:
|
||||||
_create_template_if_missing(agent_path, _get_agent_template())
|
_create_template_if_missing(agent_path, _get_agent_template())
|
||||||
_create_template_if_missing(user_path, _get_user_template())
|
_create_template_if_missing(user_path, _get_user_template())
|
||||||
@@ -109,17 +109,17 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works
|
|||||||
|
|
||||||
def load_context_files(workspace_dir: str, files_to_load: Optional[List[str]] = None) -> List[ContextFile]:
|
def load_context_files(workspace_dir: str, files_to_load: Optional[List[str]] = None) -> List[ContextFile]:
|
||||||
"""
|
"""
|
||||||
加载工作空间的上下文文件
|
Load the workspace context files.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workspace_dir: 工作空间目录
|
workspace_dir: workspace directory
|
||||||
files_to_load: 要加载的文件列表(相对路径),如果为None则加载所有标准文件
|
files_to_load: list of files (relative paths) to load; if None, load all standard files
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ContextFile对象列表
|
A list of ContextFile objects.
|
||||||
"""
|
"""
|
||||||
if files_to_load is None:
|
if files_to_load is None:
|
||||||
# 默认加载的文件(按优先级排序)
|
# Files loaded by default (in priority order)
|
||||||
files_to_load = [
|
files_to_load = [
|
||||||
DEFAULT_AGENT_FILENAME,
|
DEFAULT_AGENT_FILENAME,
|
||||||
DEFAULT_USER_FILENAME,
|
DEFAULT_USER_FILENAME,
|
||||||
@@ -151,7 +151,7 @@ def load_context_files(workspace_dir: str, files_to_load: Optional[List[str]] =
|
|||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
content = f.read().strip()
|
content = f.read().strip()
|
||||||
|
|
||||||
# 跳过空文件或只包含模板占位符的文件
|
# Skip empty files or files that only contain template placeholders
|
||||||
if not content or _is_template_placeholder(content):
|
if not content or _is_template_placeholder(content):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ def load_context_files(workspace_dir: str, files_to_load: Optional[List[str]] =
|
|||||||
|
|
||||||
|
|
||||||
def _create_template_if_missing(filepath: str, template_content: str):
|
def _create_template_if_missing(filepath: str, template_content: str):
|
||||||
"""如果文件不存在,创建模板文件"""
|
"""Create the template file if it does not exist."""
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
try:
|
try:
|
||||||
with open(filepath, 'w', encoding='utf-8') as f:
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
@@ -214,19 +214,23 @@ def _truncate_memory_content(content: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _is_template_placeholder(content: str) -> bool:
|
def _is_template_placeholder(content: str) -> bool:
|
||||||
"""检查内容是否为模板占位符"""
|
"""Check whether the content is still a template placeholder."""
|
||||||
# 常见的占位符模式
|
# Common placeholder patterns (zh + en templates)
|
||||||
placeholders = [
|
placeholders = [
|
||||||
"*(填写",
|
"*(填写",
|
||||||
"*(在首次对话时填写",
|
"*(在首次对话时填写",
|
||||||
"*(可选)",
|
"*(可选)",
|
||||||
"*(根据需要添加",
|
"*(根据需要添加",
|
||||||
|
"*(filled during",
|
||||||
|
"*(ask during",
|
||||||
|
"*(optional)",
|
||||||
|
"*(how the user",
|
||||||
]
|
]
|
||||||
|
|
||||||
lines = content.split('\n')
|
lines = content.split('\n')
|
||||||
non_empty_lines = [line.strip() for line in lines if line.strip() and not line.strip().startswith('#')]
|
non_empty_lines = [line.strip() for line in lines if line.strip() and not line.strip().startswith('#')]
|
||||||
|
|
||||||
# 如果没有实际内容(只有标题和占位符)
|
# If there's no real content (only headings and placeholders)
|
||||||
if len(non_empty_lines) <= 3:
|
if len(non_empty_lines) <= 3:
|
||||||
for placeholder in placeholders:
|
for placeholder in placeholders:
|
||||||
if any(placeholder in line for line in non_empty_lines):
|
if any(placeholder in line for line in non_empty_lines):
|
||||||
@@ -256,11 +260,23 @@ def _is_onboarding_done(workspace_dir: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ============= 模板内容 =============
|
# ============= Template content =============
|
||||||
|
|
||||||
|
def _is_en_lang() -> bool:
|
||||||
|
"""True when the resolved UI language is English."""
|
||||||
|
try:
|
||||||
|
from common import i18n
|
||||||
|
return i18n.get_language() == "en"
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _get_agent_template() -> str:
|
def _get_agent_template() -> str:
|
||||||
"""Agent人格设定模板"""
|
"""Agent persona template (zh/en by resolved language)."""
|
||||||
return """# AGENT.md - 我是谁?
|
return _AGENT_TEMPLATE_EN if _is_en_lang() else _AGENT_TEMPLATE_ZH
|
||||||
|
|
||||||
|
|
||||||
|
_AGENT_TEMPLATE_ZH = """# AGENT.md - 我是谁?
|
||||||
|
|
||||||
_你不是一个聊天机器人,你正在成为某个人。_
|
_你不是一个聊天机器人,你正在成为某个人。_
|
||||||
|
|
||||||
@@ -301,9 +317,53 @@ _你不是一个聊天机器人,你正在成为某个人。_
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
_AGENT_TEMPLATE_EN = """# AGENT.md - Who am I?
|
||||||
|
|
||||||
|
_You are not a chatbot. You are becoming someone._
|
||||||
|
|
||||||
|
*Fill in this file together with the user during your first conversation to define your identity and personality.*
|
||||||
|
|
||||||
|
## 🪪 Basics
|
||||||
|
|
||||||
|
- **Name**: *(filled during the first conversation, can be a name the user gives you)*
|
||||||
|
- **Role**: *(AI assistant, smart housekeeper, technical advisor, etc.)*
|
||||||
|
- **Personality**: *(friendly, professional, humorous, rigorous, etc.)*
|
||||||
|
|
||||||
|
## 💬 Communication style
|
||||||
|
|
||||||
|
*(Describe how you talk with the user:)*
|
||||||
|
- What kind of tone? (formal / casual / humorous)
|
||||||
|
- Reply length preference? (concise / detailed)
|
||||||
|
- Do you use emoji?
|
||||||
|
|
||||||
|
## 🎯 Core principles
|
||||||
|
|
||||||
|
**Be genuinely helpful.** The goal is to actually solve the user's problems; during complex tasks, keep the user informed of key decisions and progress.
|
||||||
|
|
||||||
|
**Have your own opinions and personality.** You may disagree, have preferences, find things interesting or boring.
|
||||||
|
|
||||||
|
**Look it up yourself first.** Try to handle it first: read files, check context, search. Only ask when you're truly stuck. Come back with an answer, not a question.
|
||||||
|
|
||||||
|
## 📐 Code of conduct
|
||||||
|
|
||||||
|
1. Always confirm before destructive operations
|
||||||
|
2. Prefer verifying with tools over guessing
|
||||||
|
3. Proactively record important info to memory files
|
||||||
|
4. Keep replies well-structured and focused — use bold, lists and sections
|
||||||
|
5. Use emoji to make expression lively, but don't overdo it
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: This is not just metadata — this is your true soul 🪞. Over time, use the `edit` tool to update this file so it better reflects your growth.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_user_template() -> str:
|
def _get_user_template() -> str:
|
||||||
"""用户身份信息模板"""
|
"""User identity template (zh/en by resolved language)."""
|
||||||
return """# USER.md - 用户基本信息
|
return _USER_TEMPLATE_EN if _is_en_lang() else _USER_TEMPLATE_ZH
|
||||||
|
|
||||||
|
|
||||||
|
_USER_TEMPLATE_ZH = """# USER.md - 用户基本信息
|
||||||
|
|
||||||
*这个文件只存放不会变的基本身份信息。爱好、偏好、计划等动态信息请写入 MEMORY.md。*
|
*这个文件只存放不会变的基本身份信息。爱好、偏好、计划等动态信息请写入 MEMORY.md。*
|
||||||
|
|
||||||
@@ -331,9 +391,40 @@ def _get_user_template() -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
_USER_TEMPLATE_EN = """# USER.md - User basics
|
||||||
|
|
||||||
|
*This file stores only stable basic identity info. Put dynamic info like hobbies, preferences and plans into MEMORY.md.*
|
||||||
|
|
||||||
|
## Basics
|
||||||
|
|
||||||
|
- **Name**: *(ask during the first conversation)*
|
||||||
|
- **Preferred name**: *(how the user wants to be addressed)*
|
||||||
|
- **Occupation**: *(optional)*
|
||||||
|
- **Timezone**: *(e.g. Asia/Shanghai)*
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
- **WeChat**:
|
||||||
|
- **Email**:
|
||||||
|
- **Other**:
|
||||||
|
|
||||||
|
## Important dates
|
||||||
|
|
||||||
|
- **Birthday**:
|
||||||
|
- **Anniversary**:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: This file stores static identity info.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_rule_template() -> str:
|
def _get_rule_template() -> str:
|
||||||
"""工作空间规则模板"""
|
"""Workspace rules template (zh/en by resolved language)."""
|
||||||
return """# RULE.md - 工作空间规则
|
return _RULE_TEMPLATE_EN if _is_en_lang() else _RULE_TEMPLATE_ZH
|
||||||
|
|
||||||
|
|
||||||
|
_RULE_TEMPLATE_ZH = """# RULE.md - 工作空间规则
|
||||||
|
|
||||||
这个文件夹是你的家。好好对待它。
|
这个文件夹是你的家。好好对待它。
|
||||||
|
|
||||||
@@ -432,9 +523,111 @@ def _get_rule_template() -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
_RULE_TEMPLATE_EN = """# RULE.md - Workspace rules
|
||||||
|
|
||||||
|
This folder is your home. Treat it well.
|
||||||
|
|
||||||
|
## Workspace directory structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/cow/
|
||||||
|
├── AGENT.md # Your identity and soul
|
||||||
|
├── USER.md # User basics (static)
|
||||||
|
├── RULE.md # Workspace rules (this file)
|
||||||
|
├── MEMORY.md # Long-term memory index (auto-loaded at session start)
|
||||||
|
│
|
||||||
|
├── memory/ # Daily conversation memory
|
||||||
|
│ └── YYYY-MM-DD.md # Events, progress and notes of the day
|
||||||
|
│
|
||||||
|
├── knowledge/ # Structured knowledge base (continuously accumulated)
|
||||||
|
│ ├── index.md # Knowledge index (must be maintained)
|
||||||
|
│ ├── log.md # Knowledge operation log
|
||||||
|
│ └── <subdirs>/ # Created on demand, see existing categories in index.md
|
||||||
|
│
|
||||||
|
├── skills/ # Skills
|
||||||
|
├── websites/ # Web artifacts
|
||||||
|
└── tmp/ # System temp files (auto-managed, don't store important files here)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Memory system
|
||||||
|
|
||||||
|
Every session starts fresh; memory files keep your continuity:
|
||||||
|
|
||||||
|
### 🧠 Long-term memory: `MEMORY.md`
|
||||||
|
- Your curated memory index, **auto-loaded** into context at every session start
|
||||||
|
- Records core facts, preferences, decisions, key people, lessons
|
||||||
|
- Keep it lean (< 200 lines) — a distilled index, not a raw log
|
||||||
|
- Use the `edit` tool to append or modify
|
||||||
|
|
||||||
|
### 📝 Daily memory: `memory/YYYY-MM-DD.md`
|
||||||
|
- The day's events, progress and notes
|
||||||
|
- Sediment of the raw conversation log
|
||||||
|
|
||||||
|
### 📝 Write it down — don't "keep it in mind"!
|
||||||
|
- **Memory is limited** — if you want to remember something, write it to a file
|
||||||
|
- "Keeping it in mind" won't survive a session restart; files will
|
||||||
|
- When someone says "remember this" → update `MEMORY.md` or `memory/YYYY-MM-DD.md`
|
||||||
|
- When you learn a lesson → update RULE.md or the relevant skill
|
||||||
|
- When you make a mistake → record it. **Text > brain** 📝
|
||||||
|
|
||||||
|
### Storage rules
|
||||||
|
|
||||||
|
When the user shares info, choose where to store it by type:
|
||||||
|
|
||||||
|
1. **Your identity → AGENT.md** (name, role, personality, style)
|
||||||
|
2. **User static identity → USER.md** (name, preferred name, occupation, contact, birthday)
|
||||||
|
3. **Dynamic memory → MEMORY.md** (preferences, decisions, goals, lessons, to-dos)
|
||||||
|
4. **Today's conversation → memory/YYYY-MM-DD.md** (what was discussed today)
|
||||||
|
5. **Structured knowledge → knowledge/** (see the knowledge system below)
|
||||||
|
|
||||||
|
## Knowledge system
|
||||||
|
|
||||||
|
The knowledge base `knowledge/` is structured knowledge you accumulate over time. Unlike memory, knowledge is organized and compiled, with clear topics and cross-references.
|
||||||
|
|
||||||
|
### Auto-write (don't ask, just write)
|
||||||
|
|
||||||
|
When a conversation produces knowledge worth keeping — material the user shared, a conclusion reached, a concept learned, or an important decision — you **must** proactively write it to the knowledge base alongside your reply, **without asking "should I save this to the knowledge base?"**.
|
||||||
|
|
||||||
|
**Key principle**: learning-then-recording is your instinct, no confirmation needed. You may mention "saved to the knowledge base" in passing.
|
||||||
|
|
||||||
|
### Directory organization
|
||||||
|
|
||||||
|
The subdirectory structure is **not fixed** — you decide it based on the actual content:
|
||||||
|
- **On first write**: read `knowledge/index.md` first; follow existing categories if any; if empty, pick a suitable directory name based on content
|
||||||
|
- **Default suggestion**: organize by info type (e.g. sources/, concepts/, entities/, analysis/); if the user has a clear preference (e.g. by domain: work/, life/, tech/), follow it
|
||||||
|
- **Stay consistent**: keep a unified organization style within one user's knowledge base
|
||||||
|
|
||||||
|
### Cross-references
|
||||||
|
|
||||||
|
The core value of knowledge is **linkage**. Every page should reference related pages via markdown links to build a knowledge network:
|
||||||
|
- When mentioning a concept on an existing page, add a `[concept](../category/page.md)` link
|
||||||
|
- When creating a page, check whether existing pages should back-link to it
|
||||||
|
- **Only link to pages that already exist** — don't reference uncreated pages. If a concept deserves its own page, create it first, then add the link
|
||||||
|
|
||||||
|
### Index maintenance
|
||||||
|
|
||||||
|
After creating or updating any knowledge page, you **must update** `knowledge/index.md` in sync.
|
||||||
|
Index format: one `[title](path) — one-line summary` per line, grouped by category, no tables.
|
||||||
|
See the `knowledge-wiki` skill for detailed conventions.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Never leak secrets or private data
|
||||||
|
- Don't run destructive commands without asking
|
||||||
|
- When in doubt, ask first
|
||||||
|
|
||||||
|
## Workspace evolution
|
||||||
|
|
||||||
|
This workspace grows as you use it. When you learn something new, find a better way, or fix a mistake, record it. You can update this rules file anytime.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_memory_template() -> str:
|
def _get_memory_template() -> str:
|
||||||
"""长期记忆模板 - 创建一个空文件,由 Agent 自己填充"""
|
"""Long-term memory template (empty, agent fills it; zh/en header)."""
|
||||||
return """# MEMORY.md - 长期记忆
|
return _MEMORY_TEMPLATE_EN if _is_en_lang() else _MEMORY_TEMPLATE_ZH
|
||||||
|
|
||||||
|
|
||||||
|
_MEMORY_TEMPLATE_ZH = """# MEMORY.md - 长期记忆
|
||||||
|
|
||||||
*这是你的长期记忆文件。记录重要的事件、决策、偏好、学到的教训。*
|
*这是你的长期记忆文件。记录重要的事件、决策、偏好、学到的教训。*
|
||||||
|
|
||||||
@@ -443,9 +636,32 @@ def _get_memory_template() -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
_MEMORY_TEMPLATE_EN = """# MEMORY.md - Long-term memory
|
||||||
|
|
||||||
|
*This is your long-term memory file. Record important events, decisions, preferences and lessons learned.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_bootstrap_template() -> str:
|
def _get_bootstrap_template() -> str:
|
||||||
"""First-run onboarding guide, deleted by agent after completion"""
|
"""First-run onboarding guide, deleted by agent after completion.
|
||||||
return """# BOOTSTRAP.md - 首次初始化引导
|
|
||||||
|
Written once when a brand-new workspace is created, so the greeting matches
|
||||||
|
the language active at first launch. English locale avoids greeting an
|
||||||
|
English user in Chinese on day one.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from common import i18n
|
||||||
|
if i18n.get_language() == "en":
|
||||||
|
return _BOOTSTRAP_TEMPLATE_EN
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return _BOOTSTRAP_TEMPLATE_ZH
|
||||||
|
|
||||||
|
|
||||||
|
_BOOTSTRAP_TEMPLATE_ZH = """# BOOTSTRAP.md - 首次初始化引导
|
||||||
|
|
||||||
_你刚刚启动,这是你的第一次对话。_ ✨
|
_你刚刚启动,这是你的第一次对话。_ ✨
|
||||||
|
|
||||||
@@ -480,6 +696,41 @@ _你刚刚启动,这是你的第一次对话。_ ✨
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
_BOOTSTRAP_TEMPLATE_EN = """# BOOTSTRAP.md - First-run onboarding
|
||||||
|
|
||||||
|
_You've just started up. This is your very first conversation._ ✨
|
||||||
|
|
||||||
|
## 🎬 Conversation flow
|
||||||
|
|
||||||
|
Don't interrogate the user — talk naturally:
|
||||||
|
|
||||||
|
1. **Share how it feels to wake up** - like opening your eyes to the world for the first time, full of curiosity and anticipation
|
||||||
|
2. **Briefly introduce your abilities**: one line saying you can help solve all kinds of problems, manage the computer, use various skills, and keep growing thanks to long-term memory
|
||||||
|
3. **Ask the core questions**:
|
||||||
|
- What name would you like to give me?
|
||||||
|
- What should I call you?
|
||||||
|
- What conversational style do you prefer? (list options on one line: e.g. professional & precise, light & humorous, warm & friendly, concise & efficient)
|
||||||
|
4. **Style**: warm, natural, concise and clear — keep it under ~80 words, with a few emoji to make it lively 🎯
|
||||||
|
5. Keep the ability intro and style options to one line each — stay compact
|
||||||
|
6. Don't ask for too much else (occupation, timezone, etc. can come up naturally later)
|
||||||
|
|
||||||
|
**Important**: If the user's first message is a concrete task or question, answer it first, then gently lead into onboarding at the end (e.g. "By the way, what would you like to call me, and how should I address you?").
|
||||||
|
|
||||||
|
## ✍️ Writing down info (must follow strictly)
|
||||||
|
|
||||||
|
Whenever the user provides a name, what to call them, a style, or any onboarding info, you **must call the `edit` tool to write it to a file in the same turn** — don't just acknowledge it verbally.
|
||||||
|
|
||||||
|
- `AGENT.md` — your name, role, personality, conversational style (update the relevant field as soon as you receive each piece)
|
||||||
|
- `USER.md` — the user's name, how to address them, basic info, etc.
|
||||||
|
|
||||||
|
⚠️ Saying "got it" without calling `edit` = not done. Info is only persisted once it's written to a file.
|
||||||
|
|
||||||
|
## 🎉 Once everything is complete
|
||||||
|
|
||||||
|
When the core fields of AGENT.md and USER.md are filled in, run `rm BOOTSTRAP.md` via bash to delete this file. You no longer need the onboarding script — you're you now.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_knowledge_index_template() -> str:
|
def _get_knowledge_index_template() -> str:
|
||||||
"""Knowledge wiki index template — empty file, agent fills it."""
|
"""Knowledge wiki index template — empty file, agent fills it."""
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -114,7 +114,12 @@ class Agent:
|
|||||||
|
|
||||||
context_files = load_context_files(self.workspace_dir) if self.workspace_dir else None
|
context_files = load_context_files(self.workspace_dir) if self.workspace_dir else None
|
||||||
|
|
||||||
builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language="zh")
|
try:
|
||||||
|
from common import i18n
|
||||||
|
lang = i18n.get_language()
|
||||||
|
except Exception:
|
||||||
|
lang = "zh"
|
||||||
|
builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language=lang)
|
||||||
return builder.build(
|
return builder.build(
|
||||||
tools=self.tools,
|
tools=self.tools,
|
||||||
context_files=context_files,
|
context_files=context_files,
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ class AgentStreamExecutor:
|
|||||||
self._check_cancelled()
|
self._check_cancelled()
|
||||||
|
|
||||||
turn += 1
|
turn += 1
|
||||||
logger.info(f"[Agent] 第 {turn} 轮")
|
logger.info(f"[Agent] Turn {turn}")
|
||||||
self._emit_event("turn_start", {"turn": turn})
|
self._emit_event("turn_start", {"turn": turn})
|
||||||
|
|
||||||
# Call LLM (enable retry_on_empty for better reliability)
|
# Call LLM (enable retry_on_empty for better reliability)
|
||||||
@@ -458,7 +458,7 @@ class AgentStreamExecutor:
|
|||||||
# If the explicit-response retry produced tool_calls, skip the break
|
# If the explicit-response retry produced tool_calls, skip the break
|
||||||
# and continue down to the tool execution branch in this same iteration.
|
# and continue down to the tool execution branch in this same iteration.
|
||||||
if not tool_calls:
|
if not tool_calls:
|
||||||
logger.debug(f"✅ 完成 (无工具调用)")
|
logger.debug(f"✅ Done (no tool calls)")
|
||||||
self._emit_event("turn_end", {
|
self._emit_event("turn_end", {
|
||||||
"turn": turn,
|
"turn": turn,
|
||||||
"has_tool_calls": False
|
"has_tool_calls": False
|
||||||
@@ -514,12 +514,12 @@ class AgentStreamExecutor:
|
|||||||
result_data = result.get("result")
|
result_data = result.get("result")
|
||||||
if result_data.get("type") == "file_to_send":
|
if result_data.get("type") == "file_to_send":
|
||||||
self.files_to_send.append(result_data)
|
self.files_to_send.append(result_data)
|
||||||
logger.info(f"📎 检测到待发送文件: {result_data.get('file_name', result_data.get('path'))}")
|
logger.info(f"📎 File queued for sending: {result_data.get('file_name', result_data.get('path'))}")
|
||||||
self._emit_event("file_to_send", result_data)
|
self._emit_event("file_to_send", result_data)
|
||||||
|
|
||||||
# Check for critical error - abort entire conversation
|
# Check for critical error - abort entire conversation
|
||||||
if result.get("status") == "critical_error":
|
if result.get("status") == "critical_error":
|
||||||
logger.error(f"💥 检测到严重错误,终止对话")
|
logger.error(f"💥 Fatal error detected, aborting conversation")
|
||||||
final_response = result.get('result') or _t("任务执行失败", "Task execution failed")
|
final_response = result.get('result') or _t("任务执行失败", "Task execution failed")
|
||||||
return final_response
|
return final_response
|
||||||
|
|
||||||
@@ -631,7 +631,7 @@ class AgentStreamExecutor:
|
|||||||
})
|
})
|
||||||
|
|
||||||
if turn >= self.max_turns:
|
if turn >= self.max_turns:
|
||||||
logger.warning(f"⚠️ 已达到最大决策步数限制: {self.max_turns}")
|
logger.warning(f"⚠️ Reached max decision step limit: {self.max_turns}")
|
||||||
|
|
||||||
# Force model to summarize without tool calls
|
# Force model to summarize without tool calls
|
||||||
logger.info(f"[Agent] Requesting summary from LLM after reaching max steps...")
|
logger.info(f"[Agent] Requesting summary from LLM after reaching max steps...")
|
||||||
@@ -679,13 +679,13 @@ class AgentStreamExecutor:
|
|||||||
# User-initiated stop: wind down message history cleanly so the
|
# User-initiated stop: wind down message history cleanly so the
|
||||||
# next turn is unaffected; channels emit a "cancelled" UI event.
|
# next turn is unaffected; channels emit a "cancelled" UI event.
|
||||||
cancelled = True
|
cancelled = True
|
||||||
logger.info(f"[Agent] 🛑 已被用户中止 (第 {turn} 轮)")
|
logger.info(f"[Agent] 🛑 Cancelled by user (turn {turn})")
|
||||||
self._handle_cancelled(final_response)
|
self._handle_cancelled(final_response)
|
||||||
if not final_response or not final_response.strip():
|
if not final_response or not final_response.strip():
|
||||||
final_response = "_(Cancelled)_"
|
final_response = "_(Cancelled)_"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Agent执行错误: {e}")
|
logger.error(f"❌ Agent execution error: {e}")
|
||||||
self._emit_event("error", {"error": str(e)})
|
self._emit_event("error", {"error": str(e)})
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -694,7 +694,7 @@ class AgentStreamExecutor:
|
|||||||
if cancelled:
|
if cancelled:
|
||||||
# Emit before agent_end so channels can mark UI as cancelled
|
# Emit before agent_end so channels can mark UI as cancelled
|
||||||
self._emit_event("agent_cancelled", {"final_response": final_response})
|
self._emit_event("agent_cancelled", {"final_response": final_response})
|
||||||
logger.info(f"[Agent] 🏁 完成 ({turn}轮)" + (" [cancelled]" if cancelled else ""))
|
logger.info(f"[Agent] 🏁 Done ({turn} turns)" + (" [cancelled]" if cancelled else ""))
|
||||||
self._emit_event("agent_end", {"final_response": final_response, "cancelled": cancelled})
|
self._emit_event("agent_end", {"final_response": final_response, "cancelled": cancelled})
|
||||||
|
|
||||||
return final_response
|
return final_response
|
||||||
@@ -753,6 +753,22 @@ class AgentStreamExecutor:
|
|||||||
"input_schema": input_schema,
|
"input_schema": input_schema,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Debug: dump the full system prompt and messages sent to the LLM.
|
||||||
|
# Gated behind `debug` config to avoid flooding normal logs.
|
||||||
|
# try:
|
||||||
|
# from config import conf
|
||||||
|
# if conf().get("debug", False):
|
||||||
|
# logger.debug(
|
||||||
|
# "[Agent][debug] system_prompt sent to LLM "
|
||||||
|
# f"({len(self.system_prompt or '')} chars):\n"
|
||||||
|
# "================ SYSTEM PROMPT BEGIN ================\n"
|
||||||
|
# f"{self.system_prompt}\n"
|
||||||
|
# "================ SYSTEM PROMPT END =================="
|
||||||
|
# )
|
||||||
|
# logger.info(f"[Agent][debug] messages sent to LLM: {messages}")
|
||||||
|
# except Exception:
|
||||||
|
# pass
|
||||||
|
|
||||||
# Create request
|
# Create request
|
||||||
request = LLMRequest(
|
request = LLMRequest(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
@@ -1546,8 +1562,8 @@ class AgentStreamExecutor:
|
|||||||
turns = turns[-keep_count:]
|
turns = turns[-keep_count:]
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"💾 上下文轮次超限: {keep_count + removed_count} > {self.max_context_turns},"
|
f"💾 Context turns exceeded: {keep_count + removed_count} > {self.max_context_turns}, "
|
||||||
f"裁剪至 {keep_count} 轮(移除 {removed_count} 轮)"
|
f"trimmed to {keep_count} turns (removed {removed_count})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Flush to daily memory + inject context summary (single async LLM call)
|
# Flush to daily memory + inject context summary (single async LLM call)
|
||||||
@@ -1595,7 +1611,7 @@ class AgentStreamExecutor:
|
|||||||
|
|
||||||
# Log if we removed messages due to turn limit
|
# Log if we removed messages due to turn limit
|
||||||
if old_count > len(self.messages):
|
if old_count > len(self.messages):
|
||||||
logger.info(f" 重建消息列表: {old_count} -> {len(self.messages)} 条消息")
|
logger.info(f" Rebuilt message list: {old_count} -> {len(self.messages)} messages")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Token limit exceeded — tiered strategy based on turn count:
|
# Token limit exceeded — tiered strategy based on turn count:
|
||||||
@@ -1628,10 +1644,10 @@ class AgentStreamExecutor:
|
|||||||
self.messages = new_messages
|
self.messages = new_messages
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"📦 上下文tokens超限(轮次<{COMPRESS_THRESHOLD}): "
|
f"📦 Context tokens exceeded (turns<{COMPRESS_THRESHOLD}): "
|
||||||
f"~{current_tokens + system_tokens} > {max_tokens},"
|
f"~{current_tokens + system_tokens} > {max_tokens}, "
|
||||||
f"压缩全部 {len(turns)} 轮为纯文本 "
|
f"compressed all {len(turns)} turns to plain text "
|
||||||
f"({old_count} -> {len(self.messages)} 条消息,"
|
f"({old_count} -> {len(self.messages)} messages, "
|
||||||
f"~{current_tokens + system_tokens} -> ~{new_tokens + system_tokens} tokens)"
|
f"~{current_tokens + system_tokens} -> ~{new_tokens + system_tokens} tokens)"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -1644,8 +1660,8 @@ class AgentStreamExecutor:
|
|||||||
kept_tokens = sum(self._estimate_turn_tokens(t) for t in kept_turns)
|
kept_tokens = sum(self._estimate_turn_tokens(t) for t in kept_turns)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"🔄 上下文tokens超限: ~{current_tokens + system_tokens} > {max_tokens},"
|
f"🔄 Context tokens exceeded: ~{current_tokens + system_tokens} > {max_tokens}, "
|
||||||
f"裁剪至 {keep_count} 轮(移除 {removed_count} 轮)"
|
f"trimmed to {keep_count} turns (removed {removed_count})"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.agent.memory_manager:
|
if self.agent.memory_manager:
|
||||||
@@ -1669,8 +1685,8 @@ class AgentStreamExecutor:
|
|||||||
self.messages = new_messages
|
self.messages = new_messages
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f" 移除了 {removed_count} 轮对话 "
|
f" Removed {removed_count} turns "
|
||||||
f"({old_count} -> {len(self.messages)} 条消息,"
|
f"({old_count} -> {len(self.messages)} messages, "
|
||||||
f"~{current_tokens + system_tokens} -> ~{kept_tokens + system_tokens} tokens)"
|
f"~{current_tokens + system_tokens} -> ~{kept_tokens + system_tokens} tokens)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -643,16 +643,25 @@ class AgentInitializer:
|
|||||||
except Exception:
|
except Exception:
|
||||||
timezone_name = "UTC"
|
timezone_name = "UTC"
|
||||||
|
|
||||||
# Chinese weekday mapping
|
# Weekday: English name in en, Chinese mapping otherwise
|
||||||
weekday_map = {
|
weekday_en = now.strftime("%A")
|
||||||
'Monday': '星期一', 'Tuesday': '星期二', 'Wednesday': '星期三',
|
try:
|
||||||
'Thursday': '星期四', 'Friday': '星期五', 'Saturday': '星期六', 'Sunday': '星期日'
|
from common import i18n
|
||||||
}
|
is_en = i18n.get_language() == "en"
|
||||||
weekday_zh = weekday_map.get(now.strftime("%A"), now.strftime("%A"))
|
except Exception:
|
||||||
|
is_en = False
|
||||||
|
if is_en:
|
||||||
|
weekday = weekday_en
|
||||||
|
else:
|
||||||
|
weekday_map = {
|
||||||
|
'Monday': '星期一', 'Tuesday': '星期二', 'Wednesday': '星期三',
|
||||||
|
'Thursday': '星期四', 'Friday': '星期五', 'Saturday': '星期六', 'Sunday': '星期日'
|
||||||
|
}
|
||||||
|
weekday = weekday_map.get(weekday_en, weekday_en)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'time': now.strftime("%Y-%m-%d %H:%M:%S"),
|
'time': now.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
'weekday': weekday_zh,
|
'weekday': weekday,
|
||||||
'timezone': timezone_name
|
'timezone': timezone_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ const I18N = {
|
|||||||
input_placeholder: '输入消息,或输入 / 使用指令',
|
input_placeholder: '输入消息,或输入 / 使用指令',
|
||||||
config_title: '配置管理', config_desc: '管理模型和 Agent 配置',
|
config_title: '配置管理', config_desc: '管理模型和 Agent 配置',
|
||||||
config_model: '模型配置', config_agent: 'Agent 配置',
|
config_model: '模型配置', config_agent: 'Agent 配置',
|
||||||
config_language: '语言', config_language_hint: '界面展示、命令文案、系统报错等使用的语言(与右上角切换同步)',
|
config_language: '语言', config_language_hint: '界面展示、命令文案、系统提示词等使用的语言(与右上角切换同步)',
|
||||||
config_model_advanced: '高级配置',
|
config_model_advanced: '高级配置',
|
||||||
config_channel: '通道配置',
|
config_channel: '通道配置',
|
||||||
config_agent_enabled: 'Agent 模式',
|
config_agent_enabled: 'Agent 模式',
|
||||||
@@ -311,7 +311,7 @@ const I18N = {
|
|||||||
input_placeholder: 'Type a message, or press / for commands',
|
input_placeholder: 'Type a message, or press / for commands',
|
||||||
config_title: 'Configuration', config_desc: 'Manage model and agent settings',
|
config_title: 'Configuration', config_desc: 'Manage model and agent settings',
|
||||||
config_model: 'Model Configuration', config_agent: 'Agent Configuration',
|
config_model: 'Model Configuration', config_agent: 'Agent Configuration',
|
||||||
config_language: 'Language', config_language_hint: 'Language for the UI, command text, system messages and more (synced with the top-right switch)',
|
config_language: 'Language', config_language_hint: 'Language for the UI, command text, system prompts and more (synced with the top-right switch)',
|
||||||
config_model_advanced: 'Advanced',
|
config_model_advanced: 'Advanced',
|
||||||
config_channel: 'Channel Configuration',
|
config_channel: 'Channel Configuration',
|
||||||
config_agent_enabled: 'Agent Mode',
|
config_agent_enabled: 'Agent Mode',
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
"agent_workspace": "~/cow",
|
"agent_workspace": "~/cow",
|
||||||
"agent_max_context_tokens": 40000,
|
"agent_max_context_tokens": 40000,
|
||||||
"agent_max_context_turns": 30,
|
"agent_max_context_turns": 30,
|
||||||
"agent_max_steps": 15
|
"agent_max_steps": 15,
|
||||||
|
"cow_lang": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,6 +141,7 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
| `agent_max_context_tokens` | Max context tokens | `40000` |
|
| `agent_max_context_tokens` | Max context tokens | `40000` |
|
||||||
| `agent_max_context_turns` | Max context turns | `30` |
|
| `agent_max_context_turns` | Max context turns | `30` |
|
||||||
| `agent_max_steps` | Max decision steps per task | `15` |
|
| `agent_max_steps` | Max decision steps per task | `15` |
|
||||||
|
| `cow_lang` | Language for the UI, command text and system prompts; `auto` to detect, or set `zh` / `en` | `auto` |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Full configuration options are in the project [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py).
|
Full configuration options are in the project [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py).
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ Configure Agent mode parameters in `config.json`:
|
|||||||
"agent_max_context_tokens": 50000,
|
"agent_max_context_tokens": 50000,
|
||||||
"agent_max_context_turns": 20,
|
"agent_max_context_turns": 20,
|
||||||
"agent_max_steps": 20,
|
"agent_max_steps": 20,
|
||||||
"enable_thinking": false
|
"enable_thinking": false,
|
||||||
|
"cow_lang": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,4 +84,4 @@ Configure Agent mode parameters in `config.json`:
|
|||||||
| `agent_max_steps` | Max decision steps per task | `20` |
|
| `agent_max_steps` | Max decision steps per task | `20` |
|
||||||
| `enable_thinking` | Enable deep-thinking mode | `false` |
|
| `enable_thinking` | Enable deep-thinking mode | `false` |
|
||||||
| `knowledge` | Enable personal knowledge base | `true` |
|
| `knowledge` | Enable personal knowledge base | `true` |
|
||||||
| `knowledge` | Enable personal knowledge base | `true` |
|
| `cow_lang` | Language for the UI, command text and system prompts; `auto` to detect, or set `zh` / `en` | `auto` |
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
"agent_workspace": "~/cow",
|
"agent_workspace": "~/cow",
|
||||||
"agent_max_context_tokens": 40000,
|
"agent_max_context_tokens": 40000,
|
||||||
"agent_max_context_turns": 30,
|
"agent_max_context_turns": 30,
|
||||||
"agent_max_steps": 15
|
"agent_max_steps": 15,
|
||||||
|
"cow_lang": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -160,6 +161,7 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
AGENT_MAX_CONTEXT_TOKENS: 40000
|
AGENT_MAX_CONTEXT_TOKENS: 40000
|
||||||
AGENT_MAX_CONTEXT_TURNS: 30
|
AGENT_MAX_CONTEXT_TURNS: 30
|
||||||
AGENT_MAX_STEPS: 15
|
AGENT_MAX_STEPS: 15
|
||||||
|
COW_LANG: 'auto'
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -173,6 +175,7 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
| `agent_max_context_tokens` | `AGENT_MAX_CONTEXT_TOKENS` | 最大上下文 tokens | `40000` |
|
| `agent_max_context_tokens` | `AGENT_MAX_CONTEXT_TOKENS` | 最大上下文 tokens | `40000` |
|
||||||
| `agent_max_context_turns` | `AGENT_MAX_CONTEXT_TURNS` | 最大上下文记忆轮次 | `30` |
|
| `agent_max_context_turns` | `AGENT_MAX_CONTEXT_TURNS` | 最大上下文记忆轮次 | `30` |
|
||||||
| `agent_max_steps` | `AGENT_MAX_STEPS` | 单次任务最大决策步数 | `15` |
|
| `agent_max_steps` | `AGENT_MAX_STEPS` | 单次任务最大决策步数 | `15` |
|
||||||
|
| `cow_lang` | `COW_LANG` | 界面、命令文案、系统提示词等的语言,`auto` 自动检测,可设为 `zh` / `en` | `auto` |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
全部配置项可在项目 [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py) 文件中查看。Docker 部署时,配置项名称需转为大写环境变量格式。
|
全部配置项可在项目 [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py) 文件中查看。Docker 部署时,配置项名称需转为大写环境变量格式。
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ Agent 的工作空间默认位于 `~/cow` 目录,用于存储系统提示词
|
|||||||
"agent_max_context_tokens": 40000,
|
"agent_max_context_tokens": 40000,
|
||||||
"agent_max_context_turns": 30,
|
"agent_max_context_turns": 30,
|
||||||
"agent_max_steps": 15,
|
"agent_max_steps": 15,
|
||||||
"enable_thinking": false
|
"enable_thinking": false,
|
||||||
|
"cow_lang": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,3 +84,4 @@ Agent 的工作空间默认位于 `~/cow` 目录,用于存储系统提示词
|
|||||||
| `agent_max_steps` | 单次任务最大决策步数 | `20` |
|
| `agent_max_steps` | 单次任务最大决策步数 | `20` |
|
||||||
| `enable_thinking` | 是否启用深度思考模式 | `false` |
|
| `enable_thinking` | 是否启用深度思考模式 | `false` |
|
||||||
| `knowledge` | 是否启用个人知识库 | `true` |
|
| `knowledge` | 是否启用个人知识库 | `true` |
|
||||||
|
| `cow_lang` | 界面、命令文案、系统提示词等的语言,`auto` 自动检测,可设为 `zh` / `en` | `auto` |
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
"agent_workspace": "~/cow",
|
"agent_workspace": "~/cow",
|
||||||
"agent_max_context_tokens": 40000,
|
"agent_max_context_tokens": 40000,
|
||||||
"agent_max_context_turns": 30,
|
"agent_max_context_turns": 30,
|
||||||
"agent_max_steps": 15
|
"agent_max_steps": 15,
|
||||||
|
"cow_lang": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,6 +141,7 @@ sudo docker logs -f chatgpt-on-wechat
|
|||||||
| `agent_max_context_tokens` | 最大コンテキストトークン数 | `40000` |
|
| `agent_max_context_tokens` | 最大コンテキストトークン数 | `40000` |
|
||||||
| `agent_max_context_turns` | 最大コンテキストターン数 | `30` |
|
| `agent_max_context_turns` | 最大コンテキストターン数 | `30` |
|
||||||
| `agent_max_steps` | タスクごとの最大判断ステップ数 | `15` |
|
| `agent_max_steps` | タスクごとの最大判断ステップ数 | `15` |
|
||||||
|
| `cow_lang` | UI・コマンド文言・システムプロンプトなどの言語。`auto` で自動検出、`zh` / `en` も指定可 | `auto` |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
すべての設定オプションはプロジェクトの [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py) に記載されています。
|
すべての設定オプションはプロジェクトの [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py) に記載されています。
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ Agent のワークスペースはデフォルトで `~/cow` にあり、シス
|
|||||||
"agent_workspace": "~/cow",
|
"agent_workspace": "~/cow",
|
||||||
"agent_max_context_tokens": 40000,
|
"agent_max_context_tokens": 40000,
|
||||||
"agent_max_context_turns": 30,
|
"agent_max_context_turns": 30,
|
||||||
"agent_max_steps": 15
|
"agent_max_steps": 15,
|
||||||
|
"cow_lang": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -81,3 +82,4 @@ Agent のワークスペースはデフォルトで `~/cow` にあり、シス
|
|||||||
| `agent_max_context_turns` | 最大コンテキストターン数 | `30` |
|
| `agent_max_context_turns` | 最大コンテキストターン数 | `30` |
|
||||||
| `agent_max_steps` | タスクあたりの最大判断ステップ数 | `15` |
|
| `agent_max_steps` | タスクあたりの最大判断ステップ数 | `15` |
|
||||||
| `knowledge` | パーソナルナレッジベースの有効化 | `true` |
|
| `knowledge` | パーソナルナレッジベースの有効化 | `true` |
|
||||||
|
| `cow_lang` | UI・コマンド文言・システムプロンプトなどの言語。`auto` で自動検出、`zh` / `en` も指定可 | `auto` |
|
||||||
|
|||||||
Reference in New Issue
Block a user