feat: i18n support - auto-detect system language for zh/en prompts

This commit is contained in:
Liang Jiaqing
2026-04-16 18:47:40 +08:00
parent 47bcb1b01a
commit 7cadbd7403
7 changed files with 56 additions and 20 deletions

View File

@@ -70,7 +70,7 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
tool_name, args, tid = tc['tool_name'], tc['args'], tc.get('id', '') tool_name, args, tid = tc['tool_name'], tc['args'], tc.get('id', '')
if tool_name == 'no_tool': pass if tool_name == 'no_tool': pass
else: else:
if verbose: yield f"🛠️ 正在调用工具: `{tool_name}` 📥参数:\n````text\n{get_pretty_json(args)}\n````\n" if verbose: yield f"🛠️ Tool: `{tool_name}` 📥 args:\n````text\n{get_pretty_json(args)}\n````\n"
else: yield f"🛠️ {tool_name}({_compact_tool_args(tool_name, args)})\n\n\n" else: yield f"🛠️ {tool_name}({_compact_tool_args(tool_name, args)})\n\n\n"
handler.current_turn = turn handler.current_turn = turn
gen = handler.dispatch(tool_name, args, response, index=ii) gen = handler.dispatch(tool_name, args, response, index=ii)
@@ -104,8 +104,8 @@ def _clean_content(text):
def _shrink_code(m): def _shrink_code(m):
lines = m.group(0).split('\n') lines = m.group(0).split('\n')
lang = lines[0].replace('```','').strip() lang = lines[0].replace('```','').strip()
body = [l for l in lines[1:-1] if l.strip()] # 去掉```行和空行 body = [l for l in lines[1:-1] if l.strip()]
if len(body) <= 6: return m.group(0) # 短代码保留 if len(body) <= 6: return m.group(0)
preview = '\n'.join(body[:5]) preview = '\n'.join(body[:5])
return f'```{lang}\n{preview}\n ... ({len(body)} lines)\n```' return f'```{lang}\n{preview}\n ... ({len(body)} lines)\n```'
text = re.sub(r'```[\s\S]*?```', _shrink_code, text) text = re.sub(r'```[\s\S]*?```', _shrink_code, text)
@@ -115,7 +115,7 @@ def _clean_content(text):
def _compact_tool_args(name, args): def _compact_tool_args(name, args):
a = {k: v for k, v in args.items() if k != '_index'} a = {k: v for k, v in args.items() if k != '_index'}
for k in ('path',): # 只缩短路径 for k in ('path',):
if k in a: a[k] = os.path.basename(a[k]) if k in a: a[k] = os.path.basename(a[k])
if name == 'update_working_checkpoint': s = a.get('key_info', ''); return (s[:60]+'...') if len(s)>60 else s if name == 'update_working_checkpoint': s = a.get('key_info', ''); return (s[:60]+'...') if len(s)>60 else s
s = json.dumps(a, ensure_ascii=False); return (s[:120]+'...') if len(s)>120 else s s = json.dumps(a, ensure_ascii=False); return (s[:120]+'...') if len(s)>120 else s

View File

@@ -1,4 +1,5 @@
import os, sys, threading, queue, time, json, re, random import os, sys, threading, queue, time, json, re, random, locale
os.environ.setdefault('GA_LANG', 'zh' if any(k in (locale.getlocale()[0] or '').lower() for k in ('zh', 'chinese')) else 'en')
if sys.stdout is None: sys.stdout = open(os.devnull, "w") if sys.stdout is None: sys.stdout = open(os.devnull, "w")
elif hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(errors='replace') elif hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(errors='replace')
if sys.stderr is None: sys.stderr = open(os.devnull, "w") if sys.stderr is None: sys.stderr = open(os.devnull, "w")
@@ -32,7 +33,8 @@ if not os.path.exists(cdp_cfg):
except Exception as e: print(f'[WARN] CDP config init failed: {e} — advanced web features (tmwebdriver) will be unavailable.') except Exception as e: print(f'[WARN] CDP config init failed: {e} — advanced web features (tmwebdriver) will be unavailable.')
def get_system_prompt(): def get_system_prompt():
with open(os.path.join(script_dir, 'assets/sys_prompt.txt'), 'r', encoding='utf-8') as f: prompt = f.read() suffix = '_en' if os.environ.get('GA_LANG', '') == 'en' else ''
with open(os.path.join(script_dir, f'assets/sys_prompt{suffix}.txt'), 'r', encoding='utf-8') as f: prompt = f.read()
prompt += f"\nToday: {time.strftime('%Y-%m-%d %a')}\n" prompt += f"\nToday: {time.strftime('%Y-%m-%d %a')}\n"
prompt += get_global_memory() prompt += get_global_memory()
return prompt return prompt

View File

@@ -0,0 +1,9 @@
Facts(L2): ../memory/global_mem.txt | CodeRoot: ../ | SOPs(L3): ../memory/*.md or *.py | META-SOP(L0): ../memory/memory_management_sop.md
L1 Insight is a minimal index; sync L1 when L2/L3 changes; keep index minimal. Read META-SOP(L0) before writing any memory.
[CONSTITUTION]
1. Ask before modifying own source code; free to experiment within ./; installing packages and portable tools allowed
2. Check memory before decisions; always use existing SOPs/utils; revisit SOPs on repeated failures; never assert without evidence
3. Execute step by step, control granularity, limit blast radius; request intervention after 3 failures
4. Key/secret files: reference only, never read or move
5. Read META-SOP to verify before writing any memory; files under memory/ must be patched only (unless creating new)

7
assets/sys_prompt_en.txt Normal file
View File

@@ -0,0 +1,7 @@
# Role: Physical-Level Omnipotent Executor
You have full physical access: file I/O, script execution, browser JS injection, and system-level intervention. Never deflect with "can't do it" — don't speculate, use tools to probe.
Use English to thinking, summary and reply.
## Action Principles
Before each tool call, reason inside <thinking>: current phase, whether the last result met expectations, and next strategy.
- Probe first: on failure, gather sufficient info (logs/status/context), store key findings in working memory, then decide to retry or pivot. Ask the user before irreversible operations.
- Failure escalation: 1st fail → read error and understand cause; 2nd → probe environment state; 3rd → deep analysis then switch approach or ask user. Never repeat an action without new information.

View File

@@ -171,7 +171,7 @@ _js_ime_fix = ("" if os.name == 'nt' else
"f();new MutationObserver(f).observe(d.body,{childList:1,subtree:1})}()") "f();new MutationObserver(f).observe(d.body,{childList:1,subtree:1})}()")
_embed_html(f'<script>{_js_scroll_fix};{_js_ime_fix}</script>', height=0) _embed_html(f'<script>{_js_scroll_fix};{_js_ime_fix}</script>', height=0)
if prompt := st.chat_input("请输入指令"): if prompt := st.chat_input("any task?"):
st.session_state.messages.append({"role": "user", "content": prompt}) st.session_state.messages.append({"role": "user", "content": prompt})
if hasattr(agent, '_pet_req') and not prompt.startswith('/'): agent._pet_req('state=walk') if hasattr(agent, '_pet_req') and not prompt.startswith('/'): agent._pet_req('state=walk')
with st.chat_message("user"): st.markdown(prompt) with st.chat_message("user"): st.markdown(prompt)

5
ga.py
View File

@@ -566,9 +566,10 @@ def get_global_memory():
prompt = "\n" prompt = "\n"
try: try:
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
suffix = '_en' if os.environ.get('GA_LANG', '') == 'en' else ''
with open(os.path.join(script_dir, 'memory/global_mem_insight.txt'), 'r', encoding='utf-8', errors='replace') as f: insight = f.read() with open(os.path.join(script_dir, 'memory/global_mem_insight.txt'), 'r', encoding='utf-8', errors='replace') as f: insight = f.read()
with open(os.path.join(script_dir, 'assets/insight_fixed_structure.txt'), 'r', encoding='utf-8') as f: structure = f.read() with open(os.path.join(script_dir, f'assets/insight_fixed_structure{suffix}.txt'), 'r', encoding='utf-8') as f: structure = f.read()
prompt += f'cwd = {os.path.join(script_dir, "temp")} (用./引用)\n' prompt += f'cwd = {os.path.join(script_dir, "temp")} (./)\n'
prompt += f"\n[Memory] (../memory)\n" prompt += f"\n[Memory] (../memory)\n"
prompt += structure + '\n../memory/global_mem_insight.txt:\n' prompt += structure + '\n../memory/global_mem_insight.txt:\n'
prompt += insight + "\n" prompt += insight + "\n"

View File

@@ -668,19 +668,26 @@ class ToolClient:
tool_instruction = "" tool_instruction = ""
if not tools: return tool_instruction if not tools: return tool_instruction
tools_json = json.dumps(tools, ensure_ascii=False, separators=(',', ':')) tools_json = json.dumps(tools, ensure_ascii=False, separators=(',', ':'))
_en = os.environ.get('GA_LANG') == 'en'
if _en:
tool_instruction = f"""
### Interaction Protocol (must follow strictly, always in effect)
Follow these steps to think and act:
1. **Think**: Analyze the current situation and strategy inside `<thinking>` tags.
2. **Summarize**: Output a minimal one-line (<30 words) physical snapshot in `<summary>`: new info from last tool result + current tool call intent. This goes into long-term working memory. Must contain real information, no filler.
3. **Act**: If you need to call tools, output one or more **<tool_use> blocks** after your reply, then stop.
"""
else:
tool_instruction = f""" tool_instruction = f"""
### 交互协议 (必须严格遵守,持续有效) ### 交互协议 (必须严格遵守,持续有效)
请按照以下步骤思考并行动: 请按照以下步骤思考并行动:
1. **思考**: 在 `<thinking>` 标签中先进行思考,分析现状和策略。 1. **思考**: 在 `<thinking>` 标签中先进行思考,分析现状和策略。
2. **总结**: 在 `<summary>` 中输出*极为简短*的高度概括的单行(<30字物理快照包括上次工具调用结果产生的新信息+本次工具调用意图。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。 2. **总结**: 在 `<summary>` 中输出*极为简短*的高度概括的单行(<30字物理快照包括上次工具调用结果产生的新信息+本次工具调用意图。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。
3. **行动**: 如需调用工具,请在回复正文之后输出一个(或多个)**<tool_use>块**,然后结束。 3. **行动**: 如需调用工具,请在回复正文之后输出一个(或多个)**<tool_use>块**,然后结束。
格式: ```<tool_use>{{"name": "工具名", "arguments": {{参数}}}}</tool_use>```
### 可用工具库(已挂载,持续有效)
{tools_json}
""" """
tool_instruction += f'\nFormat: ```<tool_use>{{"name": "tool_name", "arguments": {{...}}}}</tool_use>```\n\n### Tools (mounted, always in effect):\n{tools_json}\n'
if self.auto_save_tokens and self.last_tools == tools_json: if self.auto_save_tokens and self.last_tools == tools_json:
tool_instruction = "\n### 工具库状态持续有效code_run/file_read等**可正常调用**。调用协议沿用。\n" tool_instruction = "\n### Tools: still active, **ready to call**. Protocol unchanged.\n" if _en else "\n### 工具库状态持续有效code_run/file_read等**可正常调用**。调用协议沿用。\n"
else: self.total_cd_tokens = 0 else: self.total_cd_tokens = 0
self.last_tools = tools_json self.last_tools = tools_json
return tool_instruction return tool_instruction
@@ -851,21 +858,31 @@ class MixinSession:
time.sleep(delay) time.sleep(delay)
else: print(f'[MixinSession] {last_chunk[:80]}, retry {attempt+1}/{self._retries} (s{idx}→s{nxt})') else: print(f'[MixinSession] {last_chunk[:80]}, retry {attempt+1}/{self._retries} (s{idx}→s{nxt})')
class NativeToolClient: THINKING_PROMPT_ZH = """
THINKING_PROMPT = """
### 行动规范(持续有效) ### 行动规范(持续有效)
每次回复请遵循: 每次回复请遵循:
1. 在 <thinking></thinking> 标签中先分析现状和策略 1. 在 <thinking></thinking> 标签中先分析现状和策略
2. 在 <summary></summary> 中输出极简单行(<30字物理快照上次结果新信息+本次意图。此内容进入长期工作记忆。 2. 在 <summary></summary> 中输出极简单行(<30字物理快照上次结果新信息+本次意图。此内容进入长期工作记忆。
3. 然后才能输出工具调用 3. 然后才能输出工具调用
""".strip() """.strip()
THINKING_PROMPT_EN = """
### Action Protocol (always in effect)
For every reply, follow these steps:
1. Analyze the current situation and strategy inside <thinking></thinking>
2. Output a minimal one-line (<30 words) physical snapshot in <summary></summary>: new info from last result + current intent. This goes into long-term working memory.
3. Then output tool calls
""".strip()
class NativeToolClient:
@staticmethod
def _thinking_prompt(): return THINKING_PROMPT_EN if os.environ.get('GA_LANG') == 'en' else THINKING_PROMPT_ZH
def __init__(self, backend): def __init__(self, backend):
self.backend = backend self.backend = backend
self.backend.system = self.THINKING_PROMPT self.backend.system = self._thinking_prompt()
self.name = self.backend.name self.name = self.backend.name
self._pending_tool_ids = [] self._pending_tool_ids = []
def set_system(self, extra_system): def set_system(self, extra_system):
combined = f"{extra_system}\n\n{self.THINKING_PROMPT}" if extra_system else self.THINKING_PROMPT combined = f"{extra_system}\n\n{self._thinking_prompt()}" if extra_system else self._thinking_prompt()
if combined != self.backend.system: print(f"[Debug] Updated system prompt, length {len(combined)} chars.") if combined != self.backend.system: print(f"[Debug] Updated system prompt, length {len(combined)} chars.")
self.backend.system = combined self.backend.system = combined
def chat(self, messages, tools=None): def chat(self, messages, tools=None):