第 16 章 · Print/JSON 模式与 CI 自动化
本章目标:掌握
-pprint 模式与--mode json事件流,把 pi 从"交互式工具"变成可以在 Shell 管道和 CI 流水线中无人值守运行的自动化组件。
16.1 Print 模式:一次性执行
交互模式之外,pi 最常用的形态是 print 模式:执行一个任务、输出结果、立即退出。这是所有自动化的基础:
# 基本用法:输出响应后退出(-p 是 --print 的缩写)
pi -p "用一句话总结这个仓库的用途"
# 命名一次性会话:便于事后在 ~/.pi/agent/sessions 里追溯
pi --name "release audit" -p "审计当前仓库的依赖版本"print 模式还有一个关键特性:它会读取管道 stdin 并合并进初始提示词。这让 pi 天然兼容 Unix 管道哲学:
cat README.md | pi -p "Summarize this text"
git diff | pi -p "审查这些改动是否有安全隐患"与交互模式的本质区别
非交互模式(-p、--mode json、--mode rpc)不会弹出项目信任确认框。没有已保存的信任决定时,行为由全局设置里的 defaultProjectTrust 决定:ask 和 never 会忽略项目资源,只有 always 才会信任。CI 里要么预先 /trust 保存决定,要么显式传 -a。
16.2 文件参数与组合输入
@文件 语法可以把文件直接附加进消息,配合 print 模式做批量处理非常顺手:
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 对象:
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'事件流的结构(基于官方 docs/json.md):
// 第一行永远是会话头
{"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":[]}三个必须理解的细节:
message_update只包含增量(delta),不含累计快照,流体积与文本量线性相关;要拿最终权威内容请取message_end;- 工具执行有独立事件(
tool_execution_start/update/end),end事件带isError字段,可用于统计失败的工具调用; - 失败判定看
stopReason:assistant 消息的stopReason取值包括"stop"、"length"、"toolUse"、"error"、"aborted"——CI 中应把error/aborted视为任务失败,而不是只看进程是否退出。
16.4 在管道中消费 pi
结合 jq 可以写出各种"一行流"。例如提取最终回答文本:
# 提取 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 消息上):
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 会留在进程列表里):
# .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 层的返回值:
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
fi16.6 本章小结
-pprint 模式一次执行即退出,且会把管道 stdin 合并进初始提示词;@文件参数可与 stdin、消息组合输入;--mode json输出 JSONL 事件流:首行 session 头,message_end才是权威消息,stopReason为error/aborted表示失败;- JSON 走 stdout、日志走 stderr,用
jq select(...)提取所需事件; - CI 四要素:secret 注入密钥、幂等设计、超时预算、基于事件的失败判定。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. print 模式下通过管道传入的内容会发生什么?
2. --mode json 输出中,哪条事件包含最终权威的助手消息?
3. 在 CI 中如何可靠地判定 pi 任务失败?
4. 非交互模式下没有已保存的项目信任决定时,项目资源默认会被怎样处理?
🛠️ 动手实践
- 写一条管道命令:把任意长文本交给
pi -p生成不超过 100 字的摘要,并用jq只输出 token 总数。 - 给你的 shell 写一个
ai-commit函数:git diff --staged | pi -p "根据 diff 写一条符合 Conventional Commits 的提交信息"。 - 搭建一个最小 GitHub Actions 工作流:PR 时用 pi 审查 diff 并把结论写入
$GITHUB_STEP_SUMMARY,要求设置超时且使用 secret 注入密钥。