Skip to content

第 16 章 · Print/JSON 模式与 CI 自动化

本章目标:掌握 -p print 模式与 --mode json 事件流,把 pi 从"交互式工具"变成可以在 Shell 管道和 CI 流水线中无人值守运行的自动化组件。

16.1 Print 模式:一次性执行

交互模式之外,pi 最常用的形态是 print 模式:执行一个任务、输出结果、立即退出。这是所有自动化的基础:

bash
# 基本用法:输出响应后退出(-p 是 --print 的缩写)
pi -p "用一句话总结这个仓库的用途"

# 命名一次性会话:便于事后在 ~/.pi/agent/sessions 里追溯
pi --name "release audit" -p "审计当前仓库的依赖版本"

print 模式还有一个关键特性:它会读取管道 stdin 并合并进初始提示词。这让 pi 天然兼容 Unix 管道哲学:

bash
cat README.md | pi -p "Summarize this text"
git diff | pi -p "审查这些改动是否有安全隐患"

与交互模式的本质区别

非交互模式(-p--mode json--mode rpc不会弹出项目信任确认框。没有已保存的信任决定时,行为由全局设置里的 defaultProjectTrust 决定:asknever 会忽略项目资源,只有 always 才会信任。CI 里要么预先 /trust 保存决定,要么显式传 -a

16.2 文件参数与组合输入

@文件 语法可以把文件直接附加进消息,配合 print 模式做批量处理非常顺手:

bash
pi @code.ts @test.ts "Review these files"
pi -p @screenshot.png "What's in this image?"

stdin、@文件、命令行消息三者可以自由组合,pi 会把它们合并成一次初始提示。

16.3 JSON 事件流模式

当结果需要被程序消费时,纯文本输出不够结构化。--mode json 会把全部会话事件以 JSON Lines 形式打到 stdout——每行一个 JSON 对象:

bash
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'

事件流的结构(基于官方 docs/json.md):

jsonc
// 第一行永远是会话头
{"type":"session","version":3,"id":"uuid","timestamp":"...","cwd":"/path"}

// 随后是生命周期事件
{"type":"agent_start"}
{"type":"turn_start"}
{"type":"message_update","usage":{},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
{"type":"message_end","message":{}}
{"type":"turn_end","message":{},"toolResults":[]}
{"type":"tool_execution_start","toolCallId":"...","toolName":"bash","args":{}}
{"type":"agent_end","messages":[]}

三个必须理解的细节:

  1. message_update 只包含增量(delta),不含累计快照,流体积与文本量线性相关;要拿最终权威内容请取 message_end
  2. 工具执行有独立事件tool_execution_start/update/end),end 事件带 isError 字段,可用于统计失败的工具调用;
  3. 失败判定看 stopReason:assistant 消息的 stopReason 取值包括 "stop""length""toolUse""error""aborted"——CI 中应把 error/aborted 视为任务失败,而不是只看进程是否退出。

16.4 在管道中消费 pi

结合 jq 可以写出各种"一行流"。例如提取最终回答文本:

bash
# 提取 assistant 消息的全部 text 块
pi --mode json -p "列出 src 下所有 TODO 注释" 2>/dev/null \
  | jq -r 'select(.type == "message_end" and .message.role == "assistant")
           | .message.content[]? | select(.type == "text") | .text' \
  | tail -n 40

再比如统计本次任务的 token 成本(usage 挂在每条 assistant 消息上):

bash
pi --mode json -p "解释这段报错" 2>/dev/null \
  | jq -s '[ .[] | select(.type=="message_end" and .message.role=="assistant")
            | .message.usage.totalTokens ] | add // 0'

流量分离

JSON 事件走 stdout,日志和错误信息走 stderr,所以上面示例都用 2>/dev/null 把 stderr 丢掉。反过来调试时要看 stderr 而不是 stdout。

16.5 在 CI 中运行 pi

把 pi 放进 GitHub Actions / Jenkins 这类流水线时,按下面四条原则设计:

① 密钥管理——API key 用 CI 的 secret 注入环境变量(如 ANTHROPIC_API_KEY),绝不写进仓库或命令行参数(--api-key 会留在进程列表里):

yaml
# .github/workflows/ai-review.yml 片段
- name: AI code review
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  run: |
    git diff origin/main...HEAD > /tmp/diff.txt
    cat /tmp/diff.txt | pi --no-session -p "审查这些 diff,只输出问题清单" > review.md
    cat review.md >> $GITHUB_STEP_SUMMARY

② 幂等性——同一份输入应产出可比较的结果:加 --no-session 避免污染会话目录,提示词里避免"继续上次"这类依赖历史的表述。

③ 超时与预算——LLM 调用可能远慢于普通构建步骤,务必给步骤设置超时(如 timeout-minutes: 10),并用小模型跑常规审查控制成本(--model <便宜模型>)。

④ 失败处理——官方 CLI 参考没有承诺具体的退出码语义,稳妥做法是用 JSON 模式的 stopReason/isError 判断业务成败,而不是依赖 shell 层的返回值:

bash
pi --mode json --no-session -p "$PROMPT" > /tmp/out.jsonl
if jq -e 'select(.type == "message_end") | .message.stopReason == "stop"' /tmp/out.jsonl > /dev/null; then
  echo "任务完成"
else
  echo "任务异常终止"; exit 1
fi

16.6 本章小结

  • -p print 模式一次执行即退出,且会把管道 stdin 合并进初始提示词;
  • @文件 参数可与 stdin、消息组合输入;
  • --mode json 输出 JSONL 事件流:首行 session 头,message_end 才是权威消息,stopReasonerror/aborted 表示失败;
  • JSON 走 stdout、日志走 stderr,用 jq select(...) 提取所需事件;
  • CI 四要素:secret 注入密钥、幂等设计、超时预算、基于事件的失败判定。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. print 模式下通过管道传入的内容会发生什么?

2. --mode json 输出中,哪条事件包含最终权威的助手消息?

3. 在 CI 中如何可靠地判定 pi 任务失败?

4. 非交互模式下没有已保存的项目信任决定时,项目资源默认会被怎样处理?

🛠️ 动手实践

  1. 写一条管道命令:把任意长文本交给 pi -p 生成不超过 100 字的摘要,并用 jq 只输出 token 总数。
  2. 给你的 shell 写一个 ai-commit 函数:git diff --staged | pi -p "根据 diff 写一条符合 Conventional Commits 的提交信息"
  3. 搭建一个最小 GitHub Actions 工作流:PR 时用 pi 审查 diff 并把结论写入 $GITHUB_STEP_SUMMARY,要求设置超时且使用 secret 注入密钥。