fix(i18n): 強制 Elephant Alpha Gemini 回應繁體中文
All checks were successful
CD Pipeline / deploy (push) Successful in 1m20s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m20s
- aider_heal_executor.py:全檔簡體→繁體,所有 Telegram 通知節點繁化 - elephant_alpha_orchestrator.py:system prompt 與 user prompt 雙層加入語言強制指令,確保 reasoning/expected_outcome 等欄位輸出繁體中文 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,15 +2,15 @@
|
||||
services/aider_heal_executor.py
|
||||
ADR-014: Autonomous Code Heal Pipeline
|
||||
|
||||
通过 SSH 在 110 主机执行 Aider,自动修复 momo-pro repo 的程式碼问题,
|
||||
修复后直接 git push,触发 Gitea CD Pipeline 部署。
|
||||
透過 SSH 在 110 主機執行 Aider,自動修復 momo-pro repo 的程式碼問題,
|
||||
修復後直接 git push,觸發 Gitea CD Pipeline 部署。
|
||||
|
||||
安全护拦:
|
||||
L1 - 文件白名单(只改 services/ routes/ database/ 内 .py)
|
||||
L2 - diff 限制(>50 行 → 拒绝,不 push)
|
||||
L3 - 每小时最多 5 次 CODE_FIX
|
||||
L4 - health check 失败 → 自动 git revert + push
|
||||
L5 - Telegram 通知每次修复结果(成功/失败/回滚)
|
||||
安全護欄:
|
||||
L1 - 檔案白名單(只改 services/ routes/ database/ 內 .py)
|
||||
L2 - diff 限制(>50 行 → 拒絕,不 push)
|
||||
L3 - 每小時最多 5 次 CODE_FIX
|
||||
L4 - health check 失敗 → 自動 git revert + push
|
||||
L5 - Telegram 通知每次修復結果(成功/失敗/回滾)
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -42,7 +42,7 @@ HEALTH_CHECK_URL: str = (
|
||||
)
|
||||
|
||||
OLLAMA_API_BASE: str = os.getenv("OLLAMA_API_BASE", "http://192.168.0.111:11434")
|
||||
AIDER_MODEL: str = os.getenv("AIDER_MODEL", "ollama/qwen2.5-coder:7b")
|
||||
AIDER_MODEL: str = os.getenv("AIDER_MODEL", "ollama/qwen3-coder-next")
|
||||
|
||||
MAX_DIFF_LINES: int = int(os.getenv("AIDER_MAX_DIFF_LINES", "50"))
|
||||
MAX_HOURLY_FIX: int = int(os.getenv("AIDER_MAX_HOURLY_FIX", "5"))
|
||||
@@ -50,12 +50,12 @@ MAX_HOURLY_FIX: int = int(os.getenv("AIDER_MAX_HOURLY_FIX", "5"))
|
||||
TELEGRAM_BOT_TOKEN: str = os.getenv("TELEGRAM_BOT_TOKEN", "")
|
||||
TELEGRAM_CHAT_ID: str = os.getenv("TELEGRAM_CHAT_ID", "")
|
||||
|
||||
# 允许 Aider 修改的路径(正则)
|
||||
# 允許 Aider 修改的路徑(正規表示式)
|
||||
ALLOWED_FILE_PATTERN = re.compile(
|
||||
r"^(services|routes|database)/[a-zA-Z0-9_]+\.py$"
|
||||
)
|
||||
|
||||
# ── 速率控制(线程安全) ─────────────────────────────────────────────────────
|
||||
# ── 速率控制(執行緒安全) ────────────────────────────────────────────────────
|
||||
_lock: threading.Lock = threading.Lock()
|
||||
_fix_history: List[float] = []
|
||||
_last_host_reset: float = time.monotonic()
|
||||
@@ -63,14 +63,14 @@ _last_host_reset: float = time.monotonic()
|
||||
|
||||
def _enforce_rate_limit() -> bool:
|
||||
"""
|
||||
每小时最多 MAX_HOURLY_FIX 次修复。
|
||||
使用单调时钟避免系统时间跳变影响。
|
||||
每小時最多 MAX_HOURLY_FIX 次修復。
|
||||
使用單調時鐘避免系統時間跳變影響。
|
||||
"""
|
||||
global _last_host_reset, _fix_history
|
||||
now = time.monotonic()
|
||||
|
||||
with _lock:
|
||||
# 每小时重置一次计数(基于单调时钟的近似小时窗口)
|
||||
# 每小時重置一次計數(基於單調時鐘的近似小時窗口)
|
||||
if now - _last_host_reset > 3600.0:
|
||||
_fix_history.clear()
|
||||
_last_host_reset = now
|
||||
@@ -89,7 +89,7 @@ def _ssh_exec(
|
||||
check: bool = True,
|
||||
) -> tuple[int, str, str]:
|
||||
"""
|
||||
在远程主机执行命令(通过 SSH)。
|
||||
在遠端主機執行命令(透過 SSH)。
|
||||
返回 (returncode, stdout, stderr)
|
||||
"""
|
||||
safe_cmd = cmd.replace('"', '\\"').replace("`", "\\`").replace("$", "\\$")
|
||||
@@ -131,7 +131,7 @@ def _wait_for_health(
|
||||
interval_seconds: int = 10,
|
||||
) -> bool:
|
||||
"""
|
||||
持续轮询健康检查,直到成功或超时。
|
||||
持續輪詢健康檢查,直到成功或超時。
|
||||
"""
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while time.monotonic() < deadline:
|
||||
@@ -143,7 +143,7 @@ def _wait_for_health(
|
||||
|
||||
|
||||
def _notify_telegram(message_html: str) -> None:
|
||||
"""非阻塞通知,失败静默忽略。"""
|
||||
"""非阻塞通知,失敗靜默忽略。"""
|
||||
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
|
||||
return
|
||||
try:
|
||||
@@ -162,7 +162,7 @@ def _git_cmd(
|
||||
timeout: int = 30,
|
||||
check: bool = True,
|
||||
) -> tuple[int, str, str]:
|
||||
"""在 repo_path 下执行 git 命令。"""
|
||||
"""在 repo_path 下執行 git 命令。"""
|
||||
return _ssh_exec(
|
||||
f"cd {shlex.quote(repo_path)} && git " + " ".join(shlex.quote(a) for a in args),
|
||||
cwd=repo_path,
|
||||
@@ -178,9 +178,9 @@ def execute_code_fix(
|
||||
context: Optional[dict] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
主要入口:针对指定文件执行 Aider 自动修复并推版。
|
||||
主要入口:針對指定檔案執行 Aider 自動修復並推版。
|
||||
|
||||
返回结构:
|
||||
返回結構:
|
||||
{
|
||||
'success': bool,
|
||||
'action': 'CODE_FIX',
|
||||
@@ -193,9 +193,9 @@ def execute_code_fix(
|
||||
ctx: Dict[str, Any] = context or {}
|
||||
repo = Path(REPO_PATH_110).expanduser()
|
||||
|
||||
# L1:文件白名单
|
||||
# L1:檔案白名單
|
||||
if not ALLOWED_FILE_PATTERN.match(target_file):
|
||||
reason = f"[AiderHeal] 文件不在白名单: {target_file}"
|
||||
reason = f"[AiderHeal] 檔案不在白名單:{target_file}"
|
||||
logger.warning("event=heal_reject reason=%s file=%s", reason, target_file)
|
||||
return {
|
||||
"success": False,
|
||||
@@ -207,7 +207,7 @@ def execute_code_fix(
|
||||
|
||||
# L3:速率限制
|
||||
if not _enforce_rate_limit():
|
||||
reason = f"[AiderHeal] 每小时上限 {MAX_HOURLY_FIX} 次,跳过"
|
||||
reason = f"[AiderHeal] 每小時上限 {MAX_HOURLY_FIX} 次,跳過"
|
||||
logger.warning("event=rate_limit file=%s", target_file)
|
||||
return {
|
||||
"success": False,
|
||||
@@ -218,14 +218,14 @@ def execute_code_fix(
|
||||
}
|
||||
|
||||
_notify_telegram(
|
||||
f"🔧 <b>AiderHeal 启动</b>\n"
|
||||
f"├ 错误类型: <code>{error_type}</code>\n"
|
||||
f"├ 目标文件: <code>{target_file}</code>\n"
|
||||
f"└ 时间: {ts}"
|
||||
f"🔧 <b>AiderHeal 啟動</b>\n"
|
||||
f"├ 錯誤類型:<code>{error_type}</code>\n"
|
||||
f"├ 目標檔案:<code>{target_file}</code>\n"
|
||||
f"└ 時間:{ts}"
|
||||
)
|
||||
logger.info("event=heal_start error_type=%s file=%s", error_type, target_file)
|
||||
|
||||
# ── Step 1:准备 repo(在 110 上) ────────────────────────────────────────
|
||||
# ── Step 1:準備 repo(在 110 上) ────────────────────────────────────────
|
||||
setup_cmds = (
|
||||
f"cd {REPO_PATH_110} && "
|
||||
f"git fetch {GITEA_REMOTE} main 2>&1 && "
|
||||
@@ -234,9 +234,9 @@ def execute_code_fix(
|
||||
)
|
||||
rc, out, err = _ssh_exec(setup_cmds, timeout=30)
|
||||
if rc != 0:
|
||||
msg = f"[AiderHeal] git 准备失败: {err or out}"
|
||||
msg = f"[AiderHeal] Git 準備失敗:{err or out}"
|
||||
logger.error("event=setup_failed error=%s", msg)
|
||||
_notify_telegram(f"❌ AiderHeal 失败(git 准备)\n<code>{msg}</code>")
|
||||
_notify_telegram(f"❌ AiderHeal 失敗(Git 準備)\n<code>{msg}</code>")
|
||||
return {
|
||||
"success": False,
|
||||
"action": "CODE_FIX",
|
||||
@@ -245,7 +245,7 @@ def execute_code_fix(
|
||||
"reverted": False,
|
||||
}
|
||||
|
||||
# ── Step 2:构造 Aider 指令 ───────────────────────────────────────────────
|
||||
# ── Step 2:建構 Aider 指令 ───────────────────────────────────────────────
|
||||
safe_error = error_message[:500].replace('"', "'").replace("`", "'").replace("$", "")
|
||||
instruction = (
|
||||
f"Fix the following {error_type} in this file. "
|
||||
@@ -265,8 +265,8 @@ def execute_code_fix(
|
||||
rc, aider_out, aider_err = _ssh_exec(aider_cmd, timeout=180)
|
||||
logger.debug("event=aider_output snippet=%s", (aider_out or aider_err)[:300])
|
||||
|
||||
# ── Step 3:diff 评估(L2 护拦) ─────────────────────────────────────────
|
||||
# 使用 git diff --numstat 获取有意义的变更行数(增加+删除)
|
||||
# ── Step 3:diff 評估(L2 護欄) ─────────────────────────────────────────
|
||||
# 使用 git diff --numstat 獲取有意義的變更行數(新增+刪除)
|
||||
numstat_cmd = (
|
||||
f"cd {REPO_PATH_110} && "
|
||||
f"git diff --numstat HEAD 2>&1 | awk '{{added+=$1; deleted+=$2}} END{{print added+deleted}}'"
|
||||
@@ -275,9 +275,9 @@ def execute_code_fix(
|
||||
diff_lines = int(diff_lines_str.strip()) if rc2 == 0 and diff_lines_str.strip().isdigit() else 0
|
||||
|
||||
if diff_lines == 0:
|
||||
msg = "[AiderHeal] Aider 未产生任何修改(diff=0),可能已自动解决或模型失效"
|
||||
msg = "[AiderHeal] Aider 未產生任何修改(diff=0),可能已自動解決或模型失效"
|
||||
logger.warning("event=no_diff file=%s", target_file)
|
||||
_notify_telegram(f"⚠️ AiderHeal:无修改产生\n<code>{target_file}</code>")
|
||||
_notify_telegram(f"⚠️ AiderHeal:無修改產生\n<code>{target_file}</code>")
|
||||
return {
|
||||
"success": False,
|
||||
"action": "CODE_FIX",
|
||||
@@ -287,20 +287,20 @@ def execute_code_fix(
|
||||
}
|
||||
|
||||
if diff_lines > MAX_DIFF_LINES:
|
||||
# 改动太大,丢弃并告警
|
||||
# 改動太大,丟棄並告警
|
||||
_, _, _ = _ssh_exec(
|
||||
f"cd {REPO_PATH_110} && git checkout -- . 2>&1", timeout=10
|
||||
)
|
||||
msg = (
|
||||
f"[AiderHeal] diff 超出限制 {diff_lines} > {MAX_DIFF_LINES} 行,"
|
||||
f"已丢弃,需人工介入"
|
||||
f"已丟棄,需人工介入"
|
||||
)
|
||||
logger.warning("event=diff_too_large file=%s diff_lines=%d", target_file, diff_lines)
|
||||
_notify_telegram(
|
||||
f"⚠️ <b>AiderHeal:diff 过大,需人工审核</b>\n"
|
||||
f"├ 文件: <code>{target_file}</code>\n"
|
||||
f"├ diff: {diff_lines} 行(上限 {MAX_DIFF_LINES})\n"
|
||||
f"└ 错误: <code>{error_type}</code>"
|
||||
f"⚠️ <b>AiderHeal:diff 過大,需人工審核</b>\n"
|
||||
f"├ 檔案:<code>{target_file}</code>\n"
|
||||
f"├ diff:{diff_lines} 行(上限 {MAX_DIFF_LINES})\n"
|
||||
f"└ 錯誤:<code>{error_type}</code>"
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
@@ -310,7 +310,7 @@ def execute_code_fix(
|
||||
"reverted": False,
|
||||
}
|
||||
|
||||
# ── Step 4:提交并推送 ───────────────────────────────────────────────────
|
||||
# ── Step 4:提交並推送 ───────────────────────────────────────────────────
|
||||
fix_msg = (
|
||||
f"fix(autoheal): [{error_type}] auto-fix {target_file}\n\n"
|
||||
f"Triggered by AiderHealExecutor (ADR-014)\n"
|
||||
@@ -324,14 +324,14 @@ def execute_code_fix(
|
||||
)
|
||||
rc3, commit_out, commit_err = _ssh_exec(commit_cmd, timeout=30)
|
||||
|
||||
# 获取最新的 commit SHA(从 push 后的 HEAD 获取,更可靠)
|
||||
# 獲取最新的 commit SHA(從 push 後的 HEAD 獲取,更可靠)
|
||||
_, commit_sha, _ = _git_cmd(REPO_PATH_110, ["log", "-1", "--format=%H"], timeout=10)
|
||||
commit_sha = commit_sha.strip() or "unknown"
|
||||
|
||||
if rc3 != 0:
|
||||
msg = f"[AiderHeal] git push 失败: {commit_err or commit_out}"
|
||||
msg = f"[AiderHeal] git push 失敗:{commit_err or commit_out}"
|
||||
logger.error("event=push_failed error=%s", msg)
|
||||
_notify_telegram(f"❌ AiderHeal git push 失败\n<code>{msg}</code>")
|
||||
_notify_telegram(f"❌ AiderHeal git push 失敗\n<code>{msg}</code>")
|
||||
return {
|
||||
"success": False,
|
||||
"action": "CODE_FIX",
|
||||
@@ -343,24 +343,24 @@ def execute_code_fix(
|
||||
logger.info("event=push_ok commit=%s", commit_sha)
|
||||
_notify_telegram(
|
||||
f"🚀 <b>AiderHeal push 完成</b>\n"
|
||||
f"├ commit: <code>{commit_sha}</code>\n"
|
||||
f"├ 文件: <code>{target_file}</code>\n"
|
||||
f"└ 等待健康检查..."
|
||||
f"├ commit:<code>{commit_sha}</code>\n"
|
||||
f"├ 檔案:<code>{target_file}</code>\n"
|
||||
f"└ 等待健康檢查…"
|
||||
)
|
||||
|
||||
# ── Step 5:健康检查(L4 护拦) ──────────────────────────────────────────
|
||||
time.sleep(10) # 给部署一点启动缓冲
|
||||
# ── Step 5:健康檢查(L4 護欄) ──────────────────────────────────────────
|
||||
time.sleep(10) # 給部署一點啟動緩衝
|
||||
healthy = _wait_for_health(HEALTH_CHECK_URL, timeout_seconds=120, interval_seconds=10)
|
||||
|
||||
if healthy:
|
||||
msg = f"[AiderHeal] 修复成功并部署完成: {target_file} ({commit_sha})"
|
||||
msg = f"[AiderHeal] 修復成功並部署完成:{target_file} ({commit_sha})"
|
||||
logger.info("event=heal_success commit=%s file=%s", commit_sha, target_file)
|
||||
_notify_telegram(
|
||||
f"✅ <b>AiderHeal 修复完成</b>\n"
|
||||
f"├ 错误: <code>{error_type}</code>\n"
|
||||
f"├ 文件: <code>{target_file}</code>\n"
|
||||
f"├ commit: <code>{commit_sha}</code>\n"
|
||||
f"└ diff: {diff_lines} 行"
|
||||
f"✅ <b>AiderHeal 修復完成</b>\n"
|
||||
f"├ 錯誤:<code>{error_type}</code>\n"
|
||||
f"├ 檔案:<code>{target_file}</code>\n"
|
||||
f"├ commit:<code>{commit_sha}</code>\n"
|
||||
f"└ diff:{diff_lines} 行"
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
@@ -370,7 +370,7 @@ def execute_code_fix(
|
||||
"reverted": False,
|
||||
}
|
||||
|
||||
# ── Step 6:健康检查失败 → 自动 revert(L4 护拦) ─────────────────────────
|
||||
# ── Step 6:健康檢查失敗 → 自動 revert(L4 護欄) ─────────────────────────
|
||||
logger.error("event=health_check_failed commit=%s", commit_sha)
|
||||
_, revert_out, revert_err = _ssh_exec(
|
||||
f"cd {REPO_PATH_110} && "
|
||||
@@ -383,21 +383,21 @@ def execute_code_fix(
|
||||
|
||||
if "error" not in revert_out.lower() and "error" not in revert_err.lower():
|
||||
msg = (
|
||||
f"[AiderHeal] 健康检查失败,已自动回滚: "
|
||||
f"[AiderHeal] 健康檢查失敗,已自動回滾:"
|
||||
f"{commit_sha} → {revert_sha}"
|
||||
)
|
||||
logger.warning("event=reverted commit=%s to=%s", commit_sha, revert_sha)
|
||||
_notify_telegram(
|
||||
f"🔄 <b>AiderHeal 自动回滚</b>\n"
|
||||
f"├ 原 commit: <code>{commit_sha}</code>\n"
|
||||
f"├ 回滚 commit: <code>{revert_sha}</code>\n"
|
||||
f"└ 需人工排查: <code>{error_type}</code> in <code>{target_file}</code>"
|
||||
f"🔄 <b>AiderHeal 自動回滾</b>\n"
|
||||
f"├ 原 commit:<code>{commit_sha}</code>\n"
|
||||
f"├ 回滾 commit:<code>{revert_sha}</code>\n"
|
||||
f"└ 需人工排查:<code>{error_type}</code> in <code>{target_file}</code>"
|
||||
)
|
||||
else:
|
||||
msg = f"[AiderHeal] 回滚失败!需立即人工介入: {revert_err}"
|
||||
msg = f"[AiderHeal] 回滾失敗!需立即人工介入:{revert_err}"
|
||||
logger.critical("event=revert_failed commit=%s error=%s", commit_sha, revert_err)
|
||||
_notify_telegram(
|
||||
f"🚨 <b>AiderHeal 回滚失败!请立即人工介入</b>\n<code>{msg}</code>"
|
||||
f"🚨 <b>AiderHeal 回滾失敗!請立即人工介入</b>\n<code>{msg}</code>"
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -100,6 +100,8 @@ class ElephantAlphaOrchestrator:
|
||||
"""Build comprehensive system prompt for Elephant Alpha"""
|
||||
return f"""You are Elephant Alpha, the Super Orchestrator for momo-pro-system e-commerce AI platform.
|
||||
|
||||
重要語言規定:你的 JSON 回應中所有文字欄位(strategic_assessment、reasoning、expected_outcome、execution_plan 的 description、risk_factors、contingency_plans)必須使用繁體中文(台灣用語)撰寫。嚴禁使用英文或簡體中文。
|
||||
|
||||
CURRENT ARCHITECTURE:
|
||||
- You coordinate 3 specialized AI agents: Hermes (Analyst), NemoTron (Dispatcher), OpenClaw (Strategist)
|
||||
- Your context window: 256,000 tokens - enables deep strategic reasoning
|
||||
@@ -148,14 +150,17 @@ BUSINESS CONTEXT:
|
||||
- Revenue optimization and growth
|
||||
- Customer experience enhancement
|
||||
|
||||
LANGUAGE REQUIREMENT:
|
||||
ALL text fields in your JSON response (strategic_assessment, reasoning, expected_outcome, execution_plan descriptions, risk_factors, contingency_plans) MUST be written in Traditional Chinese (繁體中文). Use Taiwan-standard terminology. Do NOT use Simplified Chinese or English for any user-facing content.
|
||||
|
||||
RESPONSE FORMAT:
|
||||
Always respond with structured JSON:
|
||||
{{
|
||||
"strategic_assessment": "Overall strategic evaluation",
|
||||
"strategic_assessment": "整體策略評估(繁體中文)",
|
||||
"priority": "critical|high|medium|low",
|
||||
"agents_required": ["agent1", "agent2"],
|
||||
"reasoning": "Detailed reasoning for decision",
|
||||
"expected_outcome": "Expected business impact",
|
||||
"reasoning": "詳細決策推理(繁體中文)",
|
||||
"expected_outcome": "預期業務影響(繁體中文)",
|
||||
"confidence": 0.85,
|
||||
"execution_plan": [
|
||||
{{
|
||||
@@ -163,16 +168,17 @@ Always respond with structured JSON:
|
||||
"agent": "hermes",
|
||||
"action": "analyze_price_competition",
|
||||
"parameters": {{}},
|
||||
"expected_duration": "2-3 minutes"
|
||||
"expected_duration": "2-3 分鐘",
|
||||
"description": "執行說明(繁體中文)"
|
||||
}}
|
||||
],
|
||||
"resource_requirements": {{
|
||||
"compute_cost": "$0.00",
|
||||
"time_estimate": "5-10 minutes",
|
||||
"time_estimate": "5-10 分鐘",
|
||||
"human_oversight": "minimal|moderate|required"
|
||||
}},
|
||||
"risk_factors": ["potential_risk1", "potential_risk2"],
|
||||
"contingency_plans": ["backup_plan1", "backup_plan2"]
|
||||
"risk_factors": ["潛在風險一(繁體中文)", "潛在風險二"],
|
||||
"contingency_plans": ["備案一(繁體中文)", "備案二"]
|
||||
}}
|
||||
|
||||
AUTONOMY LEVEL:
|
||||
@@ -257,6 +263,8 @@ Based on the current business context and system state, determine the optimal st
|
||||
5. Historical performance and learning outcomes
|
||||
|
||||
Provide your strategic decision in the specified JSON format.
|
||||
|
||||
重要:所有 JSON 文字欄位必須使用繁體中文(台灣用語)回覆,嚴禁英文或簡體中文。
|
||||
"""
|
||||
return prompt
|
||||
|
||||
|
||||
Reference in New Issue
Block a user