ChatGPT CLI 开发实战:从零构建高效命令行交互工具
背景与痛点:为什么 CLI 也需要 AI
日常开发中,我们早已习惯在终端里敲命令:查日志、跑脚本、打包镜像。但遇到“人话”需求时,CLI 瞬间抓瞎。
- 想快速生成一段正则,得打开浏览器搜 StackOverflow,再复制回来;
- 新人问“这段报错啥意思”,你把错误贴到网页版 ChatGPT,等回答,再贴回终端;
- 写脚本时临时要一段 Python 工具函数,切屏、查文档、切回来,思路早断了。
现有 CLI 工具对 AI 的集成普遍“浅”:要么只是打开网页的快捷方式,要么把 GPT 当“高级 man 手册”,用完即走,上下文无法沉淀,更谈不上自动化。
CLI 的真正优势是可脚本化、可管道化、可组合。把 GPT 能力无缝嵌进这条流水线,才能让 AI 成为“终端原生”生产力。
技术选型:为什么选 Python,而不是 Go/Rust/Node
CLI 领域 Go 和 Rust 确实风头正劲,静态编译、单文件分发很香。但本次目标首要是**“快糙好”地验证想法**,Python 在三方面更占优:
- 生态:官方
openai包更新最及时,社区示例最多; - 交互:readline、prompt-toolkit 一键打造语法高亮、历史补全;
- 脚本友好:后续要把 CLI 嵌进 CI/CD,Python 已是 Jenkins/GitHub Actions 的默认环境,无需额外装二进制。
如果日后需要极致冷启动速度,再把核心逻辑迁到 Rust 也不迟。先跑起来,再谈优化。
核心实现:30 行代码跑通最小可用原型
下面示例基于openai>=1.0,Python 3.9+。全部单文件,可直接python -m pip install openai click rich后运行。
#!/usr/bin/env python3 # chatgpt_cli.py import os, sys, json, time from typing import List import click from rich.console import Console from rich.markdown import Markdown import openai console = Console() # ---------- 0. 配置 ---------- openai.api_key = os.getenv("OPENAI_API_KEY") if not openai.api_key: console.print("[red]请先 export OPENAI_API_KEY[/red]") sys.exit(1) MODEL = "gpt-3.5-turbo" MAX_TOKENS = 2048 TEMPERATURE = 0.7 # ---------- 1. 核心调用 ---------- def chat(messages: List[dict]) -> str: """发消息列表,返回答案字符串。""" try: resp = openai.ChatCompletion.create( model=MODEL, messages=messages, temperature=TEMPERATURE, max_tokens=MAX_TOKENS, stream=False, ) return resp.choices[0].message.content except openai.error.OpenAIError as e: console.print(f"[red]API 错误: {e}[/red]") sys.exit(2) # ---------- 2. CLI 入口 ---------- @click.command() @click.option("-s", "--system", help="系统提示语,可用来设定角色") @click.argument("question", required=True) def main(question: str, system: str): """简单问答模式""" messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": question}) with console.status("思考中…", spinner="dots"): answer = chat(messages) console.print(Markdown(answer)) if __name__ == "__main__": main()运行示例:
export OPENAI_API_KEY="sk-xxx" ./chatgpt_cli.py -s "你是一名资深运维" "写一段 shell 脚本,循环检测 80 端口,挂了就重启 nginx"终端会实时返回答案,且支持 Markdown 高亮。至此,最小闭环已跑通。
功能进化:让 CLI 更像“对话”而非“一次性问答”
原型只能单轮,实际开发中我们希望:
- 多轮上下文记忆
- 命令历史、自动补全
- 结果可管道接后续命令
下面给出增量代码片段(仍保持单文件,方便阅读):
# ---------- 3. 交互式 REPL ---------- import atexit import readline HISTORY_FILE = os.path.expanduser("~/.chatgpt_cli_history") if os.path.exists(HISTORY_FILE): readline.read_history_file(HISTORY_FILE) atexit.register(readline.write_history_file, HISTORY_FILE) def repl(system: str): messages = [] if system: messages.append({"role": "system", "content": system}) while True: try: question = input(">>> ").strip() except (EOFError, KeyboardInterrupt): console.print("\n[yellow]Bye~[/yellow]") break if question == "/exit": break if question == "/clear": messages = [] console.print("[cyan]上下文已清空[/cyan]") continue messages.append({"role": "user", "content": question}) answer = chat(messages) console.print(Markdown(answer)) messages.append({"role": "assistant", "content": answer})把main()稍作分支,即可通过chatgpt_cli.py --repl进入交互模式。/clear清空上下文,/exit退出,历史文件落盘,体验接近 IPython。
性能优化:让批量提问不再“排队”
当脚本一次性喂给 CLI 几十条需求时,串行请求会等到天荒地老。优化三板斧:
- 请求批量化
官方 ChatCompletion 暂不支持真·批量,但可把并发+限速做成“伪批”:
from concurrent.futures import ThreadPoolExecutor, as_completed import openai.error def chat_safe(msg): for attempt in range(1, 4_backoff + 1): try: return chat(msg) except openai.error.RateLimitError: time.sleep(2 ** attempt) # 指数退避 raise RuntimeError("重试超限") with ThreadPoolExecutor(max_workers=8) as pool: futures = [pool.submit(chat_safe, m) for m in batch_messages] for f in as_completed(futures): console.print(f.result())缓存策略
对“相同问题重复问”场景,用 8 KB 以内的小文件做本地 LRU 缓存,键取hashlib.sha(question).hexdigest(),TTL 设 24 h,可省约 30 % 流量。流式解析
把stream=True打开,边收边渲染,用户侧感知延迟从 2 s 降到 0.3 s;rich的Live组件可一行行刷,体验更丝滑。
安全考量:密钥与隐私
- 绝不把
OPENAI_API_KEY写死到代码或--key参数,推荐使用python-dotenv加载.env,并在.gitignore中忽略; - 涉及用户日志、对话记录,默认落盘前用
symmetric encryption(Fernet)加密,密钥走系统 keyring; - 若在企业内网运行,通过
OPENAI_BASE_URL指向自建代理网关,统一做脱敏与审计,避免直接暴露外网。
避坑指南:踩过的坑与现场急救
编码陷阱
Windows PowerShell 默认 GBK,Python 3.9+ 建议set PYTHONUTF8=1,否则中文截断会抛UnicodeEncodeError。长文本截断
GPT-3.5 最大 4 k token,输入长时先估算tiktoken长度,超了自动分段摘要,再拼成最终答案,否则后半段会被静默丢弃。速率限制
免费层 3 RPM / 150 K TPM,批量任务提前time.sleep(random.uniform(0.2,0.5))做 jitter;命中 429 后按Retry-After回退。JSON 解析
当response_format={ "type": "json_object" }时,模型偶尔返回带前缀的 JSON,如"Here is the result:\n{\"key\":1}",需正则提取,否则json.loads直接炸。
扩展思考:把 CLI 嵌进 CI/CD
Commit Message 生成
在pre-push钩子中调用chatgpt_cli --system "根据 diff 生成 50 字内 commit message,不含句号" < diff.txt,输出直接喂给git commit -m。Code Review 机器人
在 GitHub Action 里拉取 PR diff,按文件级切块并发请求,让 GPT 给出评审意见,结果以 Review Comment 形式 POST,实现“零人力”CR。单元测试补全
将函数源码喂给 CLI,prompt 设定“为以下函数生成 pytest 用例,覆盖边界”,输出写回test_*.py,再跑pytest,覆盖率瞬间拉满。
以上场景只需在 Workflow 中加三行:
- name: Install chatgpt-cli run: pip install git+https://github.com/yourname/chatgpt_cli@main - name: Gen commit message run: echo "${{ steps.diff.outputs.text }}" | chatgpt_cli -s "生成 commit message"小结
把 ChatGPT 搬进终端,不是炫技,而是让 AI 真正“长”在工作流里:
- 省掉来回切屏的 30 秒,乘以一天 50 次,就是 25 分钟;
- 让脚本也能“开口说话”,把自然语言变成可版本化的代码;
- 通过缓存、并发、批量化,把本需网页手工点的操作,浓缩成一条可重用的命令。
如果你也想亲手把“豆包”塞进终端,不妨试试这个动手实验:
从0打造个人豆包实时通话AI
实验里把 ASR、LLM、TTS 串成一条低延迟语音管道,步骤很细,跟着敲完代码,你会对“让 AI 听懂人话、开口说话”有更具象的感受。
我完整跑了一遍,大概两杯咖啡的时间就能搞定,小白也能顺利体验。祝玩得开心,终端见!