Просмотр исходного кода

docs: add gemini jsonl batch workflow

tukuaiai 1 месяц назад
Родитель
Сommit
9ec111b74c

+ 1 - 0
AGENTS.md

@@ -17,6 +17,7 @@
 - `make lint`:使用 `markdownlint-cli` 校验全仓库 Markdown。
 - `make build` / `make test` / `make clean`:目前为占位。
 - 提示词转换:`cd libs/external/prompts-library && python main.py`。
+- JSONL 批处理(Gemini):`python libs/external/prompts-library/scripts/gemini_jsonl_batch.py --input 2 --output 2/prompts.jsonl`。
 - 备份:`bash backups/一键备份.sh` 或 `python backups/快速备份.py`。
 
 ## Coding Style & Naming Conventions

+ 47 - 29
i18n/zh/documents/02-方法论/GEMINI-HEADLESS.md

@@ -1,42 +1,60 @@
-# Gemini 无头模式翻译指引
+# Gemini 无头模式 JSONL 规范化指引
 
-目标:在本地使用 Gemini CLI(gemini-2.5-flash)完成无交互批量翻译,避免工具调用与权限弹窗,适用于 prompts/skills/文档的快速机翻初稿
+目标:在本地使用 Gemini CLI(gemini-2.5-flash)批量将提示词内容转换为标准 JSONL(`{"title": "...", "content": "..."}`),全程无交互、禁止工具调用,输出可直接落盘
 
-## 原理概述
-- CLI 通过本地缓存的 Google 凭证直连 Gemini API,模型推理在云端完成
-- 使用 `--allowed-tools ''` 关闭工具调用,确保只返回纯文本,不触发 shell/浏览器等动作
-- 通过标准输入传入待翻译文本,标准输出获取结果,便于脚本流水线处理
-- 可设置代理(http/https)让请求走本地代理节点,提升成功率与稳定性
+## 工作原理
+- Gemini CLI 无独立 system slot,使用“位置参数 prompt”承载系统提示词;待处理文本通过 stdin 传入
+- 通过 `--allowed-tools ''` 关闭工具调用,确保只返回模型文本
+- 选用 `--output-format text`,配合系统提示词的“纯 JSONL 输出”约束,得到一行一个 JSON 对象
+- 代理变量可选(`http_proxy/https_proxy`),按实际网络需要设置
 
-## 基本命令
+## 系统提示词(请原样传入)
+```text
+{"category_id": 1, "category": "JSONL规范化", "row": 2, "col": 1, "title": "# JSONL 提示词转换器 - 系统提示词", "content": "# JSONL 提示词转换器 - 系统提示词\n\n你是一个专业 的提示词格式转换器。将用户提供的提示词内容转换为标准 JSONL 格式。\n\n## 输出格式\n\n```json\n{\"title\": \"<标题>\", \"content\": \"<完整内容>\"}\n```\n\n### 字段说明\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `title` | string | 提示词标题,取内容的第一行或前 50 字符 |\n| `content` | string | 完整的提示词内容 |\n\n## 转换规则\n\n1. **标题提取**:\n   - 若内容以 `#` 开头,取第一个标题作为 title\n   - 否则取前 50 字符(去除换行)\n2. **内容转义**:\n   - 换行符 转为 `\\n`\n   - 双引号转为 `\\\"`\n   - 反斜杠转为 `\\\\`\n\n## 输出要求\n\n- 每行一个完整的 JSON 对象\n- 不要添加任何解释、注释或额外文字\n- 不要用 ```json 代码块包裹\n- 直接输出纯 JSONL 内容\n\n## 示例\n\n### 输入\n```\n# Role:智能文档助手\n\n## Background\n用户需要一个能够处理文档的 AI 助手。\n\n## Skills\n-  文档解析\n- 格式转换\n```\n\n### 输出\n```\n{\"title\": \"# Role:智能文档助手\", \"content\": \"# Role:智能文档助手\\n\\n## Background\\n用户需要一个能够处 理文档的 AI 助手。\\n\\n## Skills\\n- 文档解析\\n- 格式转换\"}\n```\n\n---\n\n现在,请将用户提供的内容转换为标准 JSONL 格式。"}
+```
+
+## 单文件示例
 ```bash
-# 代理(如需)
-export http_proxy=http://127.0.0.1:9910
-export https_proxy=http://127.0.0.1:9910
+SYS_PROMPT_JSONL=$(cat <<'EOF'
+{"category_id": 1, "category": "JSONL规范化", "row": 2, "col": 1, "title": "# JSONL 提示词转换器 - 系统提示词", "content": "# JSONL 提示词转换器 - 系统提示词\n\n你是一个专业 的提示词格式转换器。将用户提供的提示词内容转换为标准 JSONL 格式。\n\n## 输出格式\n\n```json\n{\"title\": \"<标题>\", \"content\": \"<完整内容>\"}\n```\n\n### 字段说明\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `title` | string | 提示词标题,取内容的第一行或前 50 字符 |\n| `content` | string | 完整的提示词内容 |\n\n## 转换规则\n\n1. **标题提取**:\n   - 若内容以 `#` 开头,取第一个标题作为 title\n   - 否则取前 50 字符(去除换行)\n2. **内容转义**:\n   - 换行符 转为 `\\n`\n   - 双引号转为 `\\\"`\n   - 反斜杠转为 `\\\\`\n\n## 输出要求\n\n- 每行一个完整的 JSON 对象\n- 不要添加任何解释、注释或额外文字\n- 不要用 ```json 代码块包裹\n- 直接输出纯 JSONL 内容\n\n## 示例\n\n### 输入\n```\n# Role:智能文档助手\n\n## Background\n用户需要一个能够处理文档的 AI 助手。\n\n## Skills\n-  文档解析\n- 格式转换\n```\n\n### 输出\n```\n{\"title\": \"# Role:智能文档助手\", \"content\": \"# Role:智能文档助手\\n\\n## Background\\n用户需要一个能够处 理文档的 AI 助手。\\n\\n## Skills\\n- 文档解析\\n- 格式转换\"}\n```\n\n---\n\n现在,请将用户提供的内容转换为标准 JSONL 格式。"}
+EOF
+)
 
-# 单条示例:中文 -> 英文
-printf '你好,翻译成英文。' | gemini -m gemini-2.5-flash \
+# 单条转换(stdin 输入提示词内容,stdout 得到单行 JSON)
+cat 2/ASCII图生成.md | gemini -m gemini-2.5-flash \
   --output-format text \
   --allowed-tools '' \
-  "Translate this to English."
+  "$SYS_PROMPT_JSONL"
 ```
-- 提示语放在位置参数即可(`-p/--prompt` 已被标记弃用)
-- 输出为纯文本,可重定向保存
+- CLI 会把系统提示词当作主提示,stdin 内容被模型视为“用户提供的待转换文本”
+- 若需要代理,可在命令前设置 `http_proxy/https_proxy`
 
-## 批量翻译文件示例(stdin → stdout)
+## 批量处理目录 `2/` → 生成 `2/prompts.jsonl`
 ```bash
-src=i18n/zh/prompts/README.md
-dst=i18n/en/prompts/README.md
-cat "$src" | gemini -m gemini-2.5-flash --output-format text --allowed-tools '' \
-  "Translate to English; keep code fences unchanged." > "$dst"
+SYS_PROMPT_JSONL=... # 同上
+out=2/prompts.jsonl
+: > "$out"
+for f in 2/*.md; do
+  [ -f "$f" ] || continue
+  cat "$f" | gemini -m gemini-2.5-flash \
+    --output-format text \
+    --allowed-tools '' \
+    "$SYS_PROMPT_JSONL" >> "$out"
+done
 ```
-- 可在脚本中循环多个文件;失败时检查退出码与输出。
+- 确保输出文件不被再次作为输入(可在循环中过滤 `*.jsonl`)。
+- 完成后可用 `wc -l 2/prompts.jsonl` 验证行数应等于处理的文件数。
+
+## 质量校验清单
+- 每行必须是合法 JSON,对象包含 `title` 与 `content` 两个字段。
+- 不得出现额外解释、空行或代码块定界符。
+- `title` 取首个 `#` 标题或去除换行后的前 50 字符;`content` 保留原文并正确转义。
+- 建议随机抽查 2–3 行,确认换行、引号、反斜杠均被转义为 `\\n` / `\\\"` / `\\\\`。
 
-## 与现有 l10n-tool 的搭配
-- l10n-tool(deep-translator)用于全量机翻;若质量或连通性不稳,可改为逐文件走 Gemini CLI。
-- 流程:`cat 源文件 | gemini ... > 目标文件`;必要时在其他语种目录放跳转说明或手动校对。
+## 常见故障
+- **输出混入 CLI 提示或日志**:确保命令中未开启 `--debug`,并避免在循环内打印 stdout。
+- **代理导致失败**:移除 `http_proxy/https_proxy` 或改用本地直连后重试。
+- **行数不匹配**:检查是否循环中包含 `*.jsonl` 自身或隐藏文件,必要时改为 `for f in 2/*.md; do ...; done`。
 
-## 注意事项
-- 确保 `gemini` 命令在 PATH 且已完成身份认证(首次运行会引导登录)。
-- 长文本建议分段,避免超时;代码块保持原样可在提示语中声明 “keep code fences unchanged”。
-- 代理端口依实际环境调整;如不需要代理,省略相关环境变量。
+## 已生成的基线文件
+- 运行批量脚本后,本仓库已生成 `2/prompts.jsonl`,涵盖 `2/` 目录下所有 `.md` 提示词,可直接复用或作为后续增量基线。

+ 135 - 0
libs/external/prompts-library/scripts/gemini_jsonl_batch.py

@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+"""
+使用 Gemini CLI 按固定系统提示词,将指定目录下的 .md 提示词批量转换为 JSONL。
+
+特点:
+- 内置系统提示词,与《Gemini 无头模式 JSONL 规范化指引》一致
+- 禁用工具调用 (--allowed-tools ''), 输出纯文本,每个文件生成一行 JSON
+- 默认输入目录为仓库根下的 `2/`,输出为 `2/prompts.jsonl`
+
+用法示例:
+  python3 gemini_jsonl_batch.py
+  python3 gemini_jsonl_batch.py --input 2 --output 2/prompts.jsonl --model gemini-2.5-flash
+"""
+from __future__ import annotations
+
+import argparse
+import subprocess
+import sys
+from pathlib import Path
+
+# ==================== 固定系统提示词 ====================
+SYS_PROMPT = """{"category_id": 1, "category": "JSONL规范化", "row": 2, "col": 1, "title": "# JSONL 提示词转换器 - 系统提示词", "content": "# JSONL 提示词转换器 - 系统提示词\n\n你是一个专业 的提示词格式转换器。将用户提供的提示词内容转换为标准 JSONL 格式。\n\n## 输出格式\n\n```json\n{\\"title\\\": \\"<标题>\\", \\"content\\\": \\"<完整内容>\\"}\n```\n\n### 字段说明\n\n| 字段 | 类型 | 说明 |\n|------|------|------|\n| `title` | string | 提示词标题,取内容的第一行或前 50 字符 |\n| `content` | string | 完整的提示词内容 |\n\n## 转换规则\n\n1. **标题提取**:\n   - 若内容以 `#` 开头,取第一个标题作为 title\n   - 否则取前 50 字符(去除换行)\n2. **内容转义**:\n   - 换行符 转为 `\\\\n`\n   - 双引号转为 `\\\\\"`\n   - 反斜杠转为 `\\\\\\\\`\n\n## 输出要求\n\n- 每行一个完整的 JSON 对象\n- 不要添加任何解释、注释或额外文字\n- 不要用 ```json 代码块包裹\n- 直接输出纯 JSONL 内容\n\n## 示例\n\n### 输入\n```\n# Role:智能文档助手\n\n## Background\n用户需要一个能够处理文档的 AI 助手。\n\n## Skills\n-  文档解析\n- 格式转换\n```\n\n### 输出\n```\n{\\"title\\\": \\"# Role:智能文档助手\\", \\"content\\\": \\"# Role:智能文档助手\\\\n\\\\n## Background\\\\n用户需要一个能够处 理文档的 AI 助手。\\\\n\\\\n## Skills\\\\n- 文档解析\\\\n- 格式转换\\"}\n```\n\n---\n\n现在,请将用户提供的内容转换为标准 JSONL 格式。"}"""
+
+
+def parse_args() -> argparse.Namespace:
+    parser = argparse.ArgumentParser(
+        description="使用 Gemini CLI 批量将 .md 提示词转换为 JSONL(固定系统提示词)。",
+    )
+    parser.add_argument(
+        "-i",
+        "--input",
+        type=Path,
+        default=Path("2"),
+        help="输入目录,遍历其中的 .md 文件(默认:仓库根目录下的 2/)",
+    )
+    parser.add_argument(
+        "-o",
+        "--output",
+        type=Path,
+        default=None,
+        help="输出 JSONL 文件路径,默认写入 <input>/prompts.jsonl",
+    )
+    parser.add_argument(
+        "-m",
+        "--model",
+        default="gemini-2.5-flash",
+        help="Gemini 模型名称(默认:gemini-2.5-flash)",
+    )
+    parser.add_argument(
+        "--gemini-cmd",
+        default="gemini",
+        help="Gemini CLI 可执行文件名或路径(默认:gemini)",
+    )
+    parser.add_argument(
+        "-v",
+        "--verbose",
+        action="store_true",
+        help="输出处理中的详细信息",
+    )
+    return parser.parse_args()
+
+
+def run_gemini(content: str, model: str, cmd: str) -> str:
+    """调用 Gemini CLI,将单个文本转换为一行 JSON。"""
+    proc = subprocess.run(
+        [
+            cmd,
+            "-m",
+            model,
+            "--output-format",
+            "text",
+            "--allowed-tools",
+            "",
+            SYS_PROMPT,
+        ],
+        input=content.encode("utf-8"),
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        check=False,
+    )
+
+    stdout = proc.stdout.decode("utf-8", errors="replace").strip()
+    stderr = proc.stderr.decode("utf-8", errors="replace").strip()
+
+    if proc.returncode != 0:
+        raise RuntimeError(f"Gemini 调用失败 (code={proc.returncode}): {stderr or '无错误输出'}")
+
+    if stderr:
+        # 某些 CLI 可能在 stderr 打印警告,保留但不中断
+        print(f"⚠️ Gemini 警告: {stderr}", file=sys.stderr)
+
+    if not stdout:
+        raise RuntimeError("Gemini 未返回内容")
+
+    # 去除多余行,只保留非空行并合并
+    lines = [ln for ln in stdout.splitlines() if ln.strip()]
+    return " ".join(lines).strip()
+
+
+def main() -> None:
+    args = parse_args()
+    input_dir = args.input.resolve()
+    if not input_dir.exists() or not input_dir.is_dir():
+        print(f"❌ 输入目录不存在: {input_dir}")
+        sys.exit(1)
+
+    output_path = args.output or (input_dir / "prompts.jsonl")
+    output_path = output_path.resolve()
+    output_path.parent.mkdir(parents=True, exist_ok=True)
+
+    md_files = sorted(f for f in input_dir.iterdir() if f.suffix == ".md")
+    if not md_files:
+        print(f"⚠️ 未找到任何 .md 文件: {input_dir}")
+        sys.exit(0)
+
+    results = []
+    for md in md_files:
+        content = md.read_text(encoding="utf-8")
+        if args.verbose:
+            print(f"→ 处理 {md.name}")
+        try:
+            json_line = run_gemini(content, args.model, args.gemini_cmd)
+            results.append(json_line)
+        except Exception as exc:  # noqa: BLE001
+            print(f"❌ 处理失败 {md.name}: {exc}", file=sys.stderr)
+
+    with output_path.open("w", encoding="utf-8") as f:
+        for line in results:
+            f.write(line + "\n")
+
+    print(f"✅ 完成:{len(results)} 条 → {output_path}")
+
+
+if __name__ == "__main__":
+    main()