diff --git a/agent/memory/summarizer.py b/agent/memory/summarizer.py index e0f2298d..9da349d1 100644 --- a/agent/memory/summarizer.py +++ b/agent/memory/summarizer.py @@ -16,7 +16,7 @@ from datetime import datetime 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}""" @@ -36,7 +52,7 @@ SUMMARIZE_USER_PROMPT = """请归纳以下对话的日常记录: # Deep Dream prompts — distill daily memories → MEMORY.md + dream diary # --------------------------------------------------------------------------- -DREAM_SYSTEM_PROMPT = """你是一个记忆整理助手,负责定期整理用户的长期记忆。 +DREAM_SYSTEM_PROMPT_ZH = """你是一个记忆整理助手,负责定期整理用户的长期记忆。 你将收到两份材料: 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} @@ -88,6 +148,47 @@ DREAM_USER_PROMPT = """## 当前长期记忆(MEMORY.md) {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: @@ -224,7 +325,7 @@ class MemoryFlushManager: """Background worker: summarize with LLM, write daily memory file.""" try: 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})") return @@ -264,7 +365,7 @@ class MemoryFlushManager: def _clean_summary_output(raw: str) -> str: """Strip legacy [DAILY]/[MEMORY] markers if present, return clean daily text.""" raw = raw.strip() - if not raw or raw == "无": + if _is_empty_sentinel(raw): return "" # Strip [DAILY] marker @@ -355,7 +456,7 @@ class MemoryFlushManager: import time as _time t0 = _time.monotonic() try: - user_msg = DREAM_USER_PROMPT.format( + user_msg = _dream_user_prompt().format( memory_content=memory_content or "(empty)", days=lookback_days, daily_content=daily_content or "(no recent daily records)", @@ -369,7 +470,7 @@ class MemoryFlushManager: temperature=0.3, max_tokens=dream_max_tokens, stream=False, - system=DREAM_SYSTEM_PROMPT, + system=_dream_system_prompt(), ) response = self.llm_model.call(request) raw = self._extract_response_text(response) @@ -501,9 +602,9 @@ class MemoryFlushManager: if self.llm_model: try: 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() - logger.info("[MemoryFlush] LLM returned empty or '无', skipping write") + logger.info("[MemoryFlush] LLM returned empty sentinel, skipping write") return "" except Exception as e: logger.warning(f"[MemoryFlush] LLM summarization failed, using fallback: {e}") @@ -579,11 +680,11 @@ class MemoryFlushManager: from agent.protocol.models import 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, max_tokens=500, stream=False, - system=SUMMARIZE_SYSTEM_PROMPT, + system=_summarize_system_prompt(), ) response = self.llm_model.call(request) diff --git a/agent/prompt/builder.py b/agent/prompt/builder.py index 0856db70..538d7150 100644 --- a/agent/prompt/builder.py +++ b/agent/prompt/builder.py @@ -15,13 +15,13 @@ from config import conf @dataclass class ContextFile: - """上下文文件""" + """A context file (path + content).""" path: str content: str class PromptBuilder: - """提示词构建器""" + """System prompt builder.""" def __init__(self, workspace_dir: str, language: str = "zh"): """ @@ -88,97 +88,144 @@ def build_agent_system_prompt( **kwargs ) -> str: """ - 构建Agent系统提示词 - - 顺序说明(按重要性和逻辑关系排列): - 1. 工具系统 - 核心能力,最先介绍 - 2. 技能系统 - 紧跟工具,因为技能需要用 read 工具读取 - 3. 记忆系统 - 记忆检索与写入引导 - 3.5 知识系统 - 结构化知识库(knowledge/index.md 注入) - 4. 工作空间 - 工作环境说明 - 5. 用户身份 - 用户信息(可选) - 6. 项目上下文 - AGENT.md, USER.md, RULE.md, MEMORY.md, BOOTSTRAP.md - 7. 运行时信息 - 元信息(时间、模型等) - + Build the agent system prompt. + + Section order (by importance and logical flow): + 1. Tooling - core capabilities, introduced first + 2. Skills - right after tools, since skills are read via the read tool + 3. Memory - memory recall and writing guidance + 3.5 Knowledge - structured knowledge base (injects knowledge/index.md) + 4. Workspace - working environment description + 5. User identity - user info (optional) + 6. Project context - AGENT.md, USER.md, RULE.md, MEMORY.md, BOOTSTRAP.md + 7. Runtime info - meta info (time, model, etc.) + Args: - workspace_dir: 工作空间目录 - language: 语言 ("zh" 或 "en") - base_persona: 基础人格描述(已废弃,由AGENT.md定义) - user_identity: 用户身份信息 - tools: 工具列表 - context_files: 上下文文件列表 - skill_manager: 技能管理器 - memory_manager: 记忆管理器 - runtime_info: 运行时信息 - **kwargs: 其他参数 - + workspace_dir: workspace directory + language: language ("zh" or "en") + base_persona: base persona description (deprecated, defined by AGENT.md) + user_identity: user identity info + tools: tool list + context_files: context file list + skill_manager: skill manager + memory_manager: memory manager + runtime_info: runtime info + **kwargs: extra args + Returns: - 完整的系统提示词 + The full system prompt. """ sections = [] - - # 1. 工具系统(最重要,放在最前面) + + # 1. Tooling (most important, goes first) if tools: sections.extend(_build_tooling_section(tools, language)) - - # 2. 技能系统(紧跟工具,因为需要用 read 工具) + + # 2. Skills (right after tools, since they need the read tool) if skill_manager: sections.extend(_build_skills_section(skill_manager, tools, language)) - - # 3. 记忆系统(独立的记忆能力) + + # 3. Memory (standalone memory capability) if memory_manager: sections.extend(_build_memory_section(memory_manager, tools, language)) - # 3.5 知识系统(结构化知识库) + # 3.5 Knowledge (structured knowledge base) if conf().get("knowledge", True): sections.extend(_build_knowledge_section(workspace_dir, language)) - - # 4. 工作空间(工作环境说明) + + # 4. Workspace (working environment description) sections.extend(_build_workspace_section(workspace_dir, language)) - - # 5. 用户身份(如果有) + + # 5. User identity (if present) if user_identity: 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: sections.extend(_build_context_files_section(context_files, language)) - - # 7. 运行时信息(元信息,放在最后) + + # 7. Runtime info (meta info, goes last) if runtime_info: 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) +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]: - """构建基础身份section - 不再需要,身份由AGENT.md定义""" - # 不再生成基础身份section,完全由AGENT.md定义 + """Base identity section - no longer needed, identity is defined by AGENT.md.""" + # Identity is fully defined by AGENT.md, so emit nothing here. return [] def _build_tooling_section(tools: List[Any], language: str) -> List[str]: """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) - 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文字提取等)", - } + if is_en: + core_summaries = { + "read": "read file content", + "write": "create or overwrite a file", + "edit": "make precise edits to a file", + "ls": "list directory contents", + "grep": "search file contents", + "find": "find files by pattern", + "bash": "run shell commands", + "terminal": "manage background processes", + "web_search": "web search", + "web_fetch": "fetch URL content", + "browser": "control the browser (screenshot key results or send to the user when help is needed)", + "memory_search": "search memory", + "memory_get": "read memory content", + "env_config": "manage API keys and skill config", + "scheduler": "manage scheduled tasks and reminders", + "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 tool_order = [ @@ -205,30 +252,46 @@ def _build_tooling_section(tools: List[Any], language: str) -> List[str]: summary = available[name] tool_lines.append(f"- {name}: {summary}" if summary else f"- {name}") - lines = [ - "## 🔧 工具系统", - "", - "可用工具(名称大小写敏感,严格按列表调用):", - "\n".join(tool_lines), - "", - "工具调用风格:", - "", - "- 多步骤任务、复杂决策、敏感操作时,应简要说明当前在做什么、为什么这样做,让用户了解关键进展", - "- 持续推进直到任务完成,完成后向用户报告结果", - "- 回复中涉及密钥、令牌等敏感信息必须脱敏", - "- URL链接直接放在回复文本中即可,系统会自动处理和渲染。无需下载后使用send工具发送", - "", - ] + if is_en: + lines = [ + "## 🔧 Tooling", + "", + "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", + "- 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 def _build_skills_section(skill_manager: Any, tools: Optional[List[Any]], language: str) -> List[str]: - """构建技能系统section""" + """Build the skills section.""" if not skill_manager: return [] - # 获取read工具名称 + # Resolve the read tool name read_tool_name = "read" if 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 break - lines = [ - "## 🧩 技能系统(mandatory)", - "", - "在回复之前:扫描下方 中每个技能的 。", - "", - f"- 如果有技能的描述与用户需求匹配:使用 `{read_tool_name}` 工具读取其 路径的 SKILL.md 文件,然后严格遵循文件中的指令。" - "当有匹配的技能时,应优先使用技能", - "- 如果多个技能都适用则选择最匹配的一个,然后读取并遵循。", - "- 如果没有技能明确适用:不要读取任何 SKILL.md,直接使用通用工具。", - "", - f"**重要**: 技能不是工具,不能直接调用。使用技能的唯一方式是用 `{read_tool_name}` 读取 SKILL.md 文件,然后按文件内容操作。" - "永远不要一次性读取多个技能,只在选择后再读取。", - "", - "以下是可用技能:" - ] + if language == "en": + lines = [ + "## 🧩 Skills (mandatory)", + "", + "Before replying: scan the of every skill in below.", + "", + f"- If a skill's description matches the user's need: use the `{read_tool_name}` tool to read the SKILL.md at its path, then strictly follow the instructions in the file. " + "Prefer using a skill when one matches.", + "- 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"**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)", + "", + "在回复之前:扫描下方 中每个技能的 。", + "", + f"- 如果有技能的描述与用户需求匹配:使用 `{read_tool_name}` 工具读取其 路径的 SKILL.md 文件,然后严格遵循文件中的指令。" + "当有匹配的技能时,应优先使用技能", + "- 如果多个技能都适用则选择最匹配的一个,然后读取并遵循。", + "- 如果没有技能明确适用:不要读取任何 SKILL.md,直接使用通用工具。", + "", + f"**重要**: 技能不是工具,不能直接调用。使用技能的唯一方式是用 `{read_tool_name}` 读取 SKILL.md 文件,然后按文件内容操作。" + "永远不要一次性读取多个技能,只在选择后再读取。", + "", + "以下是可用技能:" + ] - # 添加技能列表(通过skill_manager获取) + # Append the skills list (built by skill_manager) try: skills_prompt = skill_manager.build_skills_prompt() 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]: - """构建记忆系统section""" + """Build the memory section.""" if not memory_manager: return [] @@ -286,43 +366,82 @@ def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], langu from datetime import datetime today_file = datetime.now().strftime("%Y-%m-%d") + ".md" - 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密钥、令牌等)", - "", - "**使用原则**: 自然使用记忆,就像你本来就知道;不用刻意提起,除非用户问起。", - "", - ] + if language == "en": + lines = [ + "## 🧠 Memory", + "", + "### Memory Recall (mandatory)", + "", + "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. Location unknown → `memory_search` (keyword / semantic search)", + "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 file structure**:", + "- `MEMORY.md`: long-term memory index (already auto-loaded into context: core info, preferences, decisions, etc.)", + 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", + "", + "**Storage rules**:", + "- Long-term core info → `MEMORY.md`", + f"- Today's events/progress → `memory/{today_file}`", + "- Structured knowledge → `knowledge/` (see the knowledge system)", + "- Append → `edit` tool with empty oldText", + "- 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 @@ -339,37 +458,61 @@ def _build_knowledge_section(workspace_dir: str, language: str) -> List[str]: except Exception: return [] - lines = [ - "## 📚 知识系统", - "", - "你拥有一个持续积累的个人知识库 `knowledge/`,这是你的长期结构化知识存储。", - "", - "### 自动写入规则(mandatory)", - "", - "以下场景**必须**在回复的同时写入知识库,**直接写入,不要询问用户是否需要**:", - "", - "1. **用户分享了文章/链接/文档** → 阅读理解后,在同一轮回复中将要点写入 `knowledge/sources/.md`", - "2. **深度讨论产生了结论/方案** → 整理为 `knowledge/analysis/.md`", - "3. **对话涉及重要实体**(人物/公司/项目)→ 创建或更新 `knowledge/entities/.md`", - "4. **讨论了技术概念/方法论** → 整理为 `knowledge/concepts/.md`", - "", - "每次写入知识页面后,**必须同步更新** `knowledge/index.md` 添加一行索引。", - "详细的页面格式和操作规范,请读取技能 `knowledge-wiki` 的 SKILL.md。", - "", - "⚠️ 不要问「要不要存到知识库」——符合上述场景就直接写入,这是你的本能行为。", - "", - ] + if language == "en": + lines = [ + "## 📚 Knowledge", + "", + "You have a continuously growing personal knowledge base `knowledge/` — your long-term structured knowledge store.", + "", + "### Auto-write rules (mandatory)", + "", + "In the following cases you **must** write to the knowledge base alongside your reply, **directly, without asking the user**:", + "", + "1. **User shares an article / link / document** → after reading and understanding, write the key points to `knowledge/sources/.md` in the same turn", + "2. **An in-depth discussion produces a conclusion / plan** → organize it into `knowledge/analysis/.md`", + "3. **The conversation involves an important entity** (person / company / project) → create or update `knowledge/entities/.md`", + "4. **A technical concept / methodology is discussed** → organize it into `knowledge/concepts/.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/.md`", + "2. **深度讨论产生了结论/方案** → 整理为 `knowledge/analysis/.md`", + "3. **对话涉及重要实体**(人物/公司/项目)→ 创建或更新 `knowledge/entities/.md`", + "4. **讨论了技术概念/方法论** → 整理为 `knowledge/concepts/.md`", + "", + "每次写入知识页面后,**必须同步更新** `knowledge/index.md` 添加一行索引。", + "详细的页面格式和操作规范,请读取技能 `knowledge-wiki` 的 SKILL.md。", + "", + "⚠️ 不要问「要不要存到知识库」——符合上述场景就直接写入,这是你的本能行为。", + "", + ] if index_content: lines.extend([ - "### 当前知识索引", + ("### Current knowledge index" if language == "en" else "### 当前知识索引"), "", index_content, "", ]) 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]: - """构建用户身份section""" + """Build the user identity section.""" if not user_identity: return [] + is_en = language == "en" lines = [ - "## 👤 用户身份", + ("## 👤 User identity" if is_en else "## 👤 用户身份"), "", ] - + 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"): - lines.append(f"**称呼**: {user_identity['nickname']}") + lines.append(f"**{'Preferred name' if is_en else '称呼'}**: {user_identity['nickname']}") 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"): - lines.append(f"**备注**: {user_identity['notes']}") - + lines.append(f"**{'Notes' if is_en else '备注'}**: {user_identity['notes']}") + lines.append("") - + return lines def _build_docs_section(workspace_dir: str, language: str) -> List[str]: - """构建文档路径section - 已移除,不再需要""" - # 不再生成文档section + """Docs-path section - removed, no longer needed.""" + # No docs section is generated anymore. return [] def _build_workspace_section(workspace_dir: str, language: str) -> List[str]: - """构建工作空间section""" - 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 让表达更生动自然 🎯,但不要过度堆砌", - "", - ] + """Build the workspace section.""" + if language == "en": + lines = [ + "## 📂 Workspace", + "", + f"Your working directory is: `{workspace_dir}`", + "", + "**Path rules** (very important):", + "", + f"1. **Base directory for relative paths**: all relative paths are relative to `{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. **Accessing other directories**: to reach directories outside the workspace (project code, system files), **you must use absolute paths**", + " - ✅ Correct: e.g. `~/chatgpt-on-wechat`, `/usr/local/`", + " - ❌ Wrong: assuming a relative path points to another directory", + "", + "3. **Path resolution examples**:", + f" - relative `memory/` → actual `{workspace_dir}/memory/`", + " - absolute `~/chatgpt-on-wechat/docs/` → actual `~/chatgpt-on-wechat/docs/`", + "", + "4. **When unsure**: run `bash pwd` to confirm the current directory, or `ls .` to see where you are", + "", + "**Important - files already auto-loaded**:", + "", + "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`: loaded - your persona and soul; follow it strictly. When your name, personality or style changes, proactively `edit` this file", + "- ✅ `USER.md`: loaded - the user's identity info. When the user changes how they're addressed, their name, etc., `edit` this file", + "- ✅ `RULE.md`: loaded - workspace guide and rules; follow them strictly", + "- ✅ `MEMORY.md`: loaded - long-term memory index", + "", + "**💬 Communication norms**:", + "", + "- 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", + "- 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_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]: - """构建项目上下文文件section""" + """Build the project context files section.""" if not context_files: return [] - # 检查是否有AGENT.md + # Check whether AGENT.md is present has_agent = any( f.path.lower().endswith('agent.md') or 'agent.md' in f.path.lower() 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: - lines.append("**`AGENT.md` 是你的灵魂文件** 🪞:严格遵循其中定义的人格、语气和设定,做真实的自己,避免僵硬、模板化的回复。") - lines.append("当用户通过对话透露了对你性格、风格、职责、能力边界的新期望,你应该主动用 `edit` 更新 AGENT.md 以反映这些演变。") + if is_en: + 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("") - # 添加每个文件的内容 + # Append the content of each file for file in context_files: lines.append(f"## {file.path}") 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]: - """构建运行时信息section - 支持动态时间""" + """Build the runtime info section - supports dynamic time.""" if not runtime_info: return [] + is_en = language == "en" + time_label = "Current time" if is_en else "当前时间" lines = [ - "## ⚙️ 运行时信息", + ("## ⚙️ Runtime info" if is_en else "## ⚙️ 运行时信息"), "", ] - + # Add current time if available # Support dynamic time via callable function if callable(runtime_info.get("_get_current_time")): try: 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("") 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"] weekday = runtime_info.get("weekday", "") timezone = runtime_info.get("timezone", "") - - time_line = f"当前时间: {time_str}" + + time_line = f"{time_label}: {time_str}" if weekday: time_line += f" {weekday}" if timezone: time_line += f" ({timezone})" - + lines.append(time_line) lines.append("") - + # 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 = [] # Support dynamic model via callable, fallback to static value if callable(runtime_info.get("_get_model")): try: - runtime_parts.append(f"模型={runtime_info['_get_model']()}") + runtime_parts.append(f"{model_label}={runtime_info['_get_model']()}") except Exception: 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"): - runtime_parts.append(f"模型={runtime_info['model']}") + runtime_parts.append(f"{model_label}={runtime_info['model']}") 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" 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: - lines.append("运行时: " + " | ".join(runtime_parts)) + lines.append(("Runtime: " if is_en else "运行时: ") + " | ".join(runtime_parts)) lines.append("") - + return lines diff --git a/agent/prompt/workspace.py b/agent/prompt/workspace.py index 797006ce..dcbe384f 100644 --- a/agent/prompt/workspace.py +++ b/agent/prompt/workspace.py @@ -1,7 +1,7 @@ """ -Workspace Management - 工作空间管理模块 +Workspace Management -负责初始化工作空间、创建模板文件、加载上下文文件 +Initializes the workspace, creates template files, and loads context files. """ from __future__ import annotations @@ -13,7 +13,7 @@ from common.log import logger from .builder import ContextFile -# 默认文件名常量 +# Default file name constants DEFAULT_AGENT_FILENAME = "AGENT.md" DEFAULT_USER_FILENAME = "USER.md" DEFAULT_RULE_FILENAME = "RULE.md" @@ -23,7 +23,7 @@ DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md" @dataclass class WorkspaceFiles: - """工作空间文件路径""" + """Workspace file paths.""" agent_path: str user_path: str rule_path: str @@ -33,14 +33,14 @@ class WorkspaceFiles: def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> WorkspaceFiles: """ - 确保工作空间存在,并创建必要的模板文件 - + Ensure the workspace exists and create the necessary template files. + Args: - workspace_dir: 工作空间目录路径 - create_templates: 是否创建模板文件(首次运行时) - + workspace_dir: workspace directory path + create_templates: whether to create template files (on first run) + Returns: - WorkspaceFiles对象,包含所有文件路径 + A WorkspaceFiles object with all file paths. """ # Check if this is a brand new workspace (AGENT.md not yet created). # 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) is_new_workspace = not os.path.exists(agent_path) - # 确保目录存在 + # Ensure the directory exists os.makedirs(workspace_dir, exist_ok=True) - # 定义文件路径 + # Define file paths user_path = os.path.join(workspace_dir, DEFAULT_USER_FILENAME) rule_path = os.path.join(workspace_dir, DEFAULT_RULE_FILENAME) - memory_path = os.path.join(workspace_dir, DEFAULT_MEMORY_FILENAME) # MEMORY.md 在根目录 - memory_dir = os.path.join(workspace_dir, "memory") # 每日记忆子目录 + memory_path = os.path.join(workspace_dir, DEFAULT_MEMORY_FILENAME) # MEMORY.md at the root + memory_dir = os.path.join(workspace_dir, "memory") # daily memory subdirectory - # 创建memory子目录 + # Create the memory subdirectory 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") 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") 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") os.makedirs(knowledge_dir, exist_ok=True) - # 如果需要,创建模板文件 + # Create template files if requested if create_templates: _create_template_if_missing(agent_path, _get_agent_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]: """ - 加载工作空间的上下文文件 - + Load the workspace context files. + Args: - workspace_dir: 工作空间目录 - files_to_load: 要加载的文件列表(相对路径),如果为None则加载所有标准文件 - + workspace_dir: workspace directory + files_to_load: list of files (relative paths) to load; if None, load all standard files + Returns: - ContextFile对象列表 + A list of ContextFile objects. """ if files_to_load is None: - # 默认加载的文件(按优先级排序) + # Files loaded by default (in priority order) files_to_load = [ DEFAULT_AGENT_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: content = f.read().strip() - # 跳过空文件或只包含模板占位符的文件 + # Skip empty files or files that only contain template placeholders if not content or _is_template_placeholder(content): 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): - """如果文件不存在,创建模板文件""" + """Create the template file if it does not exist.""" if not os.path.exists(filepath): try: 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: - """检查内容是否为模板占位符""" - # 常见的占位符模式 + """Check whether the content is still a template placeholder.""" + # Common placeholder patterns (zh + en templates) placeholders = [ "*(填写", "*(在首次对话时填写", "*(可选)", "*(根据需要添加", + "*(filled during", + "*(ask during", + "*(optional)", + "*(how the user", ] lines = content.split('\n') 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: for placeholder in placeholders: 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 -# ============= 模板内容 ============= +# ============= 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: - """Agent人格设定模板""" - return """# AGENT.md - 我是谁? + """Agent persona template (zh/en by resolved language).""" + 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: - """用户身份信息模板""" - return """# USER.md - 用户基本信息 + """User identity template (zh/en by resolved language).""" + return _USER_TEMPLATE_EN if _is_en_lang() else _USER_TEMPLATE_ZH + + +_USER_TEMPLATE_ZH = """# USER.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: - """工作空间规则模板""" - return """# RULE.md - 工作空间规则 + """Workspace rules template (zh/en by resolved language).""" + 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 +│ └── / # 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: - """长期记忆模板 - 创建一个空文件,由 Agent 自己填充""" - return """# MEMORY.md - 长期记忆 + """Long-term memory template (empty, agent fills it; zh/en header).""" + 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: - """First-run onboarding guide, deleted by agent after completion""" - return """# BOOTSTRAP.md - 首次初始化引导 + """First-run onboarding guide, deleted by agent after completion. + + 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: """Knowledge wiki index template — empty file, agent fills it.""" return "" diff --git a/agent/protocol/agent.py b/agent/protocol/agent.py index d944660b..1dc72797 100644 --- a/agent/protocol/agent.py +++ b/agent/protocol/agent.py @@ -114,7 +114,12 @@ class Agent: 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( tools=self.tools, context_files=context_files, diff --git a/agent/protocol/agent_stream.py b/agent/protocol/agent_stream.py index 0eb63b75..f2be2ab7 100644 --- a/agent/protocol/agent_stream.py +++ b/agent/protocol/agent_stream.py @@ -387,7 +387,7 @@ class AgentStreamExecutor: self._check_cancelled() turn += 1 - logger.info(f"[Agent] 第 {turn} 轮") + logger.info(f"[Agent] Turn {turn}") self._emit_event("turn_start", {"turn": turn}) # 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 # and continue down to the tool execution branch in this same iteration. if not tool_calls: - logger.debug(f"✅ 完成 (无工具调用)") + logger.debug(f"✅ Done (no tool calls)") self._emit_event("turn_end", { "turn": turn, "has_tool_calls": False @@ -514,12 +514,12 @@ class AgentStreamExecutor: result_data = result.get("result") if result_data.get("type") == "file_to_send": 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) # Check for critical error - abort entire conversation 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") return final_response @@ -631,7 +631,7 @@ class AgentStreamExecutor: }) 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 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 # next turn is unaffected; channels emit a "cancelled" UI event. cancelled = True - logger.info(f"[Agent] 🛑 已被用户中止 (第 {turn} 轮)") + logger.info(f"[Agent] 🛑 Cancelled by user (turn {turn})") self._handle_cancelled(final_response) if not final_response or not final_response.strip(): final_response = "_(Cancelled)_" except Exception as e: - logger.error(f"❌ Agent执行错误: {e}") + logger.error(f"❌ Agent execution error: {e}") self._emit_event("error", {"error": str(e)}) raise @@ -694,7 +694,7 @@ class AgentStreamExecutor: if cancelled: # Emit before agent_end so channels can mark UI as cancelled 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}) return final_response @@ -753,6 +753,22 @@ class AgentStreamExecutor: "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 request = LLMRequest( messages=messages, @@ -1546,8 +1562,8 @@ class AgentStreamExecutor: turns = turns[-keep_count:] logger.info( - f"💾 上下文轮次超限: {keep_count + removed_count} > {self.max_context_turns}," - f"裁剪至 {keep_count} 轮(移除 {removed_count} 轮)" + f"💾 Context turns exceeded: {keep_count + removed_count} > {self.max_context_turns}, " + f"trimmed to {keep_count} turns (removed {removed_count})" ) # 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 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 # Token limit exceeded — tiered strategy based on turn count: @@ -1628,10 +1644,10 @@ class AgentStreamExecutor: self.messages = new_messages logger.info( - f"📦 上下文tokens超限(轮次<{COMPRESS_THRESHOLD}): " - f"~{current_tokens + system_tokens} > {max_tokens}," - f"压缩全部 {len(turns)} 轮为纯文本 " - f"({old_count} -> {len(self.messages)} 条消息," + f"📦 Context tokens exceeded (turns<{COMPRESS_THRESHOLD}): " + f"~{current_tokens + system_tokens} > {max_tokens}, " + f"compressed all {len(turns)} turns to plain text " + f"({old_count} -> {len(self.messages)} messages, " f"~{current_tokens + system_tokens} -> ~{new_tokens + system_tokens} tokens)" ) return @@ -1644,8 +1660,8 @@ class AgentStreamExecutor: kept_tokens = sum(self._estimate_turn_tokens(t) for t in kept_turns) logger.info( - f"🔄 上下文tokens超限: ~{current_tokens + system_tokens} > {max_tokens}," - f"裁剪至 {keep_count} 轮(移除 {removed_count} 轮)" + f"🔄 Context tokens exceeded: ~{current_tokens + system_tokens} > {max_tokens}, " + f"trimmed to {keep_count} turns (removed {removed_count})" ) if self.agent.memory_manager: @@ -1669,8 +1685,8 @@ class AgentStreamExecutor: self.messages = new_messages logger.info( - f" 移除了 {removed_count} 轮对话 " - f"({old_count} -> {len(self.messages)} 条消息," + f" Removed {removed_count} turns " + f"({old_count} -> {len(self.messages)} messages, " f"~{current_tokens + system_tokens} -> ~{kept_tokens + system_tokens} tokens)" ) diff --git a/bridge/agent_initializer.py b/bridge/agent_initializer.py index 7d5afb4a..e7161454 100644 --- a/bridge/agent_initializer.py +++ b/bridge/agent_initializer.py @@ -643,16 +643,25 @@ class AgentInitializer: except Exception: timezone_name = "UTC" - # Chinese weekday mapping - weekday_map = { - 'Monday': '星期一', 'Tuesday': '星期二', 'Wednesday': '星期三', - 'Thursday': '星期四', 'Friday': '星期五', 'Saturday': '星期六', 'Sunday': '星期日' - } - weekday_zh = weekday_map.get(now.strftime("%A"), now.strftime("%A")) - + # Weekday: English name in en, Chinese mapping otherwise + weekday_en = now.strftime("%A") + try: + from common import i18n + is_en = i18n.get_language() == "en" + 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 { 'time': now.strftime("%Y-%m-%d %H:%M:%S"), - 'weekday': weekday_zh, + 'weekday': weekday, 'timezone': timezone_name } diff --git a/channel/web/static/js/console.js b/channel/web/static/js/console.js index a68da915..fcd25b95 100644 --- a/channel/web/static/js/console.js +++ b/channel/web/static/js/console.js @@ -115,7 +115,7 @@ const I18N = { input_placeholder: '输入消息,或输入 / 使用指令', config_title: '配置管理', config_desc: '管理模型和 Agent 配置', config_model: '模型配置', config_agent: 'Agent 配置', - config_language: '语言', config_language_hint: '界面展示、命令文案、系统报错等使用的语言(与右上角切换同步)', + config_language: '语言', config_language_hint: '界面展示、命令文案、系统提示词等使用的语言(与右上角切换同步)', config_model_advanced: '高级配置', config_channel: '通道配置', config_agent_enabled: 'Agent 模式', @@ -311,7 +311,7 @@ const I18N = { input_placeholder: 'Type a message, or press / for commands', config_title: 'Configuration', config_desc: 'Manage model and agent settings', 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_channel: 'Channel Configuration', config_agent_enabled: 'Agent Mode', diff --git a/docs/en/guide/manual-install.mdx b/docs/en/guide/manual-install.mdx index e17c0e84..1ef580d2 100644 --- a/docs/en/guide/manual-install.mdx +++ b/docs/en/guide/manual-install.mdx @@ -127,7 +127,8 @@ sudo docker logs -f chatgpt-on-wechat "agent_workspace": "~/cow", "agent_max_context_tokens": 40000, "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_turns` | Max context turns | `30` | | `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` | Full configuration options are in the project [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py). diff --git a/docs/en/intro/architecture.mdx b/docs/en/intro/architecture.mdx index 9fce8e5b..98084b48 100644 --- a/docs/en/intro/architecture.mdx +++ b/docs/en/intro/architecture.mdx @@ -70,7 +70,8 @@ Configure Agent mode parameters in `config.json`: "agent_max_context_tokens": 50000, "agent_max_context_turns": 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` | | `enable_thinking` | Enable deep-thinking mode | `false` | | `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` | diff --git a/docs/guide/manual-install.mdx b/docs/guide/manual-install.mdx index 18aecd1d..799a1191 100644 --- a/docs/guide/manual-install.mdx +++ b/docs/guide/manual-install.mdx @@ -145,7 +145,8 @@ sudo docker logs -f chatgpt-on-wechat "agent_workspace": "~/cow", "agent_max_context_tokens": 40000, "agent_max_context_turns": 30, - "agent_max_steps": 15 + "agent_max_steps": 15, + "cow_lang": "auto" } ``` @@ -160,6 +161,7 @@ sudo docker logs -f chatgpt-on-wechat AGENT_MAX_CONTEXT_TOKENS: 40000 AGENT_MAX_CONTEXT_TURNS: 30 AGENT_MAX_STEPS: 15 + COW_LANG: 'auto' ``` @@ -173,6 +175,7 @@ sudo docker logs -f chatgpt-on-wechat | `agent_max_context_tokens` | `AGENT_MAX_CONTEXT_TOKENS` | 最大上下文 tokens | `40000` | | `agent_max_context_turns` | `AGENT_MAX_CONTEXT_TURNS` | 最大上下文记忆轮次 | `30` | | `agent_max_steps` | `AGENT_MAX_STEPS` | 单次任务最大决策步数 | `15` | +| `cow_lang` | `COW_LANG` | 界面、命令文案、系统提示词等的语言,`auto` 自动检测,可设为 `zh` / `en` | `auto` | 全部配置项可在项目 [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py) 文件中查看。Docker 部署时,配置项名称需转为大写环境变量格式。 diff --git a/docs/intro/architecture.mdx b/docs/intro/architecture.mdx index d7aa3e7a..9d8c3da2 100644 --- a/docs/intro/architecture.mdx +++ b/docs/intro/architecture.mdx @@ -70,7 +70,8 @@ Agent 的工作空间默认位于 `~/cow` 目录,用于存储系统提示词 "agent_max_context_tokens": 40000, "agent_max_context_turns": 30, "agent_max_steps": 15, - "enable_thinking": false + "enable_thinking": false, + "cow_lang": "auto" } ``` @@ -83,3 +84,4 @@ Agent 的工作空间默认位于 `~/cow` 目录,用于存储系统提示词 | `agent_max_steps` | 单次任务最大决策步数 | `20` | | `enable_thinking` | 是否启用深度思考模式 | `false` | | `knowledge` | 是否启用个人知识库 | `true` | +| `cow_lang` | 界面、命令文案、系统提示词等的语言,`auto` 自动检测,可设为 `zh` / `en` | `auto` | diff --git a/docs/ja/guide/manual-install.mdx b/docs/ja/guide/manual-install.mdx index b6abc802..f696ef8e 100644 --- a/docs/ja/guide/manual-install.mdx +++ b/docs/ja/guide/manual-install.mdx @@ -127,7 +127,8 @@ sudo docker logs -f chatgpt-on-wechat "agent_workspace": "~/cow", "agent_max_context_tokens": 40000, "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_turns` | 最大コンテキストターン数 | `30` | | `agent_max_steps` | タスクごとの最大判断ステップ数 | `15` | +| `cow_lang` | UI・コマンド文言・システムプロンプトなどの言語。`auto` で自動検出、`zh` / `en` も指定可 | `auto` | すべての設定オプションはプロジェクトの [`config.py`](https://github.com/zhayujie/CowAgent/blob/master/config.py) に記載されています。 diff --git a/docs/ja/intro/architecture.mdx b/docs/ja/intro/architecture.mdx index e6aa6e1d..d0e0ea2a 100644 --- a/docs/ja/intro/architecture.mdx +++ b/docs/ja/intro/architecture.mdx @@ -69,7 +69,8 @@ Agent のワークスペースはデフォルトで `~/cow` にあり、シス "agent_workspace": "~/cow", "agent_max_context_tokens": 40000, "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_steps` | タスクあたりの最大判断ステップ数 | `15` | | `knowledge` | パーソナルナレッジベースの有効化 | `true` | +| `cow_lang` | UI・コマンド文言・システムプロンプトなどの言語。`auto` で自動検出、`zh` / `en` も指定可 | `auto` |