数据截至 (上游 commit 0a27a45390b4)
gptme 是什么 · 全景与阅读地图
30 秒导读: gptme 是一个跑在终端里的 AI agent——你在命令行里跟它说话,它能读写文件、跑 shell、执行 Python、开浏览器,像一个坐在你旁边、有手有脚的助手。它不绑定某一家模型(Anthropic / OpenAI / 本地 llama.cpp 都行),数据和会话都留在你自己机器上。本章带你零基础认识它、看懂它的顶层结构,并告诉你想深入哪个机制该读哪一章。
1. 这是什么(零基础也能懂)
一句话定义: gptme 是一个通用型 + 编码型的命令行 AI agent——把大语言模型接上「一套能真正动手的工具」,让它在你的终端里替你干活。
它给自己的注音是 /ʤiː piː tiː miː/,README 里的自我介绍是「a personal AI agent that runs anywhere a terminal runs」(见 README.md 顶部简介)。
解决什么问题 / 给谁用。 想象你在终端里,想让 AI 帮你改一个项目的代码、跑测试、查日志、总结一个网页——但纯聊天的模型只会「说」,不会「做」:它给不了你一个真正被修改的文件,也跑不了一条命令。gptme 补的正是这一步:把模型说的话,落成真实的动作。它面向:
- 想在终端 / SSH / tmux / CI 里用 agent 的工程师;
- 想要一个不锁定厂商、数据留本地的 Claude Code / Cursor / Codex 替代品的人;
- 想把「agent 循环」这套东西读明白、甚至二次开发的人。
它能做什么(功能):
- 在对话里执行 shell 命令、运行 Python 代码;
- 读取、 保存、打补丁式地编辑文件;
- 浏览网页、看图(vision)、截图;
- 换用不同的模型提供商(Anthropic / OpenAI / Google / xAI / DeepSeek / OpenRouter / 本地);
- 通过钩子(hooks)、插件、**技能(skills)**扩展行为。
用起来什么样。 一个最小的真实交互——你在命令行敲:
$ gptme "把 README 里的拼写错误修好" README.md
gptme 会:把 README.md 拉进上下文 → 让模型思考 → 模型回一段话,里面夹着一个 shell 或 patch 代码块 → gptme 就地执行这个代码块 → 把执行结果贴回对话 → 继续,直到任务完成。CLI 的帮助文本本身就点明了这个定位:「gptme is a chat-CLI for LLMs, empowering them with tools to run shell commands, execute code, read and manipulate files」(gptme/cli/main.py:434-435,docstring)。
一句话直觉 / 类比。 把普通聊天模型当成「只有嘴、没有手」的大脑;gptme 给它接上了手脚(工具)、记忆(会话日志)和一条不断循环的神经(主循环)。它最巧妙的一点是:模型不需要特殊的函数调用协议——它只要在回答里写一个 Markdown 代码块,gptme 就把那当成一次工具调用去执行。
2. 顶层全景(它大概怎么转)
这一节讲「大盘」:gptme 由哪些部件组成、一个用户输入是怎么端到端走完的。
2.1 部件职责一览
| 部件 | 干什么 | 在哪(主要文件/符号) |
|---|---|---|
| CLI 入口 | 解析命令行参数、装配配置与工具,最后调用 chat() | gptme/cli/main.py:727 main → :1170 chat(...) |
| 聊天循环 | 会话主循环:取输入 → 生成 → 执行工具 → 判断是否继续 | gptme/chat.py:55 chat、:176 _run_chat_loop、:333 _process_message_conversation |
| 单步 step | 一次「生成 + 执行工具」的原子步骤 | gptme/chat.py:579 step |
| LLM 抽象 | 把统一的消息发给任意提供商,拿回一条 assistant 消息 | gptme/llm/__init__.py:279 reply → :629 _reply_stream / :394 _chat_complete |
| 工具系统 | 把回复里的代码块解析成 ToolUse、判断可否运行、执行 | gptme/tools/base.py:711 ToolUse、gptme/tools/__init__.py:324 execute_msg |
| 内置工具 | shell / python / 文件编辑 / 浏览器等具体「手脚 」 | gptme/tools/(shell.py、python.py、patch.py、browser.py …) |
| 提示与上下文 | 组装分层系统提示;发送前做上下文注入/压缩 | gptme/prompts/__init__.py:479 get_prompt、gptme/logmanager/manager.py:911 prepare_messages |
| 消息与持久化 | 消息数据模型 + 会话日志(JSONL / TOML / 事件日志) | gptme/message.py:251 Message、gptme/logmanager/(manager.py、eventlog.py) |
| 钩子系统 | 在循环各阶段挂载确认、护栏、上下文注入等 | gptme/hooks/registry.py:654 trigger_hook、gptme/hooks/types.py:66 HookType |
2.2 端到端走一个 turn(顶层流程图)
怎么读这张图: 从上往下是一次用户输入引发的完整过程;右侧的循环箭头是关键——只要模型这一步还留着「可运行的工具」,就不回头问用户,而是自动再走一圈。
用户在终端输入一句话
│
▼
┌──────────────────────────────────────────────┐
│ 聊天主循环 _run_chat_loop (chat.py:176) │
│ 取到用户消息 → append 进会话日志 │
└───────────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ 处理一个 turn _process_message_conversation │◀──────────────┐
│ (chat.py:333) │ │
│ ┌────────────────────────────────────────┐ │ │
│ │ 单步 step (chat.py:521) │ │ │
│ │ ① prepare_messages 组装/压缩上下文 │ │ │
│ │ ② reply 生成 assistant 回复 │ │ │
│ │ ③ execute_msg 解析并执行工具块 │ │ │
│ └──────────────┬─────────────────────────┘ │ │
│ │ 工具结果回灌进日志(append) │ │
│ ▼ │ │
│ has_runnable? 回复里还有可运行的工具吗? │ 是,继续下一步 │
│ (chat.py:433, ToolUse.iter_from_content)├───────────────┘
└───────────────┬────────────────────────────────┘
│ 否:这一 turn 结束
▼
回到主循环,等下一次用户输入
2.3 主线走一遍(高层,不进代码)
把上图翻成一句话叙述,分三段看:
① 准备。 用户输入被包成一条 Message,追加进会话日志;发给模型前,prepare_messages 会把系统提示、文件内容等上下文注入进来,并在超预算时做压缩/裁剪(prepare_messages,gptme/logmanager/manager.py:911)。
② 生成。 reply 把这批消息交给对应提供商的后端(流式走 _reply_stream,非流式走 _chat_complete),拿回一条 assistant 消息——里面可能夹着一个或多个工具代码块(reply,gptme/llm/__init__.py:279)。
③ 执行并决定是否继续。 execute_msg 从回复内容里 iter_from_content 出所有 ToolUse,逐个执行,把结果作为新消息回灌;然后 _process_message_conversation 检查最后一条 assistant 消息里还有没有可运行的工具(has_runnable)——有就自动再走一步,没有才把控制权还给用户(gptme/chat.py:486-495)。
3. 这套设计的精华速览
不必读代码也能带走的三个「为什么」:
-
代码块即工具调用。 gptme 不强依赖各家的 function-calling 协议:模型只要写一个带语言标签的 Markdown 代码块(如
```shell),ToolUse._from_codeblock就按语言标签找到对应工具并执行(gptme/tools/base.py:916_from_codeblock、:949iter_from_content)。这让它天然 provider-agnostic——换模型不换执行机制。详见 02-tool-system.md。 -
「有没有可运行工具」是循环的方向盘。 一个 turn 要不要继续,唯一判据是
has_runnable:模型自己通过「是否再写一个工具块」来决定要不要继续动手。这就是 agent「自主推进」的最小内核(gptme/chat.py:491-494)。详见 01-agent-loop.md。 -
提示分层 + 发送前才组装。 系统提示分成核心身份、用户偏好、工具说明、项目上下文等若干层(
get_prompt的文档字符串,gptme/prompts/__init__.py:494-548);而 file 内容/RAG 等易变上下文是在每次发送前由prepare_messages动态注入并压缩的,历史日志本身保持干净。详见 04-prompt-context.md。
4. 阅读地图(想深入哪块读哪章)
本子库分六章,按由浅入深排序。各章一句话:
| 章节 | 讲什么 | 什么时候读 |
|---|---|---|
| index.md(本章) | gptme 是什么、顶层结构、阅读地图 | 先读我,建立全局认知 |
| 01-agent-loop.md | 主循环如何 generate → 执行工具 → 回灌,has_runnable 如何决定继续/停止 | 想理解「agent 为什么能自主多步推进」 |
| 02-tool-system.md | 为什么代码块等于工具调用;ToolUse 的解析、ToolSpec 的注册与分发 | 想理解工具机制、或想写一个自定义工具 |
| 03-builtin-tools.md | shell / python / 文件编辑(patch/save) / 浏览器等具体工具的行为与边界 | 想知道 agent 的「手脚」各自能干什么、怎么落地 |
| 04-prompt-context.md | 系统提示分层、prepare_messages 的上下文注入与压缩 | 想理解「模型到底看到了什么」以及上下文预算 |
| 05-hooks-extensibility.md | 钩子在循环各阶段的挂载点、确认(confirm)护栏、插件/技能 | 想给 gptme 加护栏、加行为、做二次开发 |
| 06-message-persistence.md | Message 模型、会话日志(JSONL/TOML)、事件日志与分支 | 想理解会话如何存盘、恢复、可审计 |
建议顺序: index → 01 → 02 → 03,是「主干」;想扩展/定制再看 04 → 05 → 06。
5. 边界与说明(读之前先知道)
- 本章是导航层,刻意不深入任何单一机制的实现细节——那些留给各章。这里只帮你低成本判断「该读哪章」。
- gptme 是活跃开发中的大项目(仓库还含
server/、webui/、tauri/、acp/、eval/、mcp/等本子库未覆盖的子系统);本子库聚焦终端 agent 的核心链路(CLI → 循环 → 工具 → LLM → 提示 → 持久化),不覆盖 Web UI、桌面壳、评测框架等。 - 所有引用均以
sourceCommit: 8d974ca1为准;上游更新后行号可能漂移,请优先用符号名(下表)定位。
6. 代码地图(导航索引)
一张跳转表——想读源码时,按「符号名」grep 最抗行号漂移。
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| CLI 入口(装配后调 chat) | gptme/cli/main.py | main / chat(...) 调用点 |
| 聊天主循环 | gptme/chat.py | chat / _run_chat_loop |
| 一个 turn 的处理 | gptme/chat.py | _process_message_conversation |
| 单步:生成+执行 | gptme/chat.py | step |
| 是否继续的判据 | gptme/chat.py | has_runnable(用 ToolUse.iter_from_content) |
| LLM 统一入口 | gptme/llm/__init__.py | reply / _reply_stream / _chat_complete |
| 工具执行分发 | gptme/tools/__init__.py | execute_msg / get_tools / get_available_tools |
| 工具调用数据模型 | gptme/tools/base.py | ToolUse / iter_from_content / _from_codeblock / is_runnable |
| 工具规格与注册 | gptme/tools/base.py | ToolSpec |
| 系统提示组装 | gptme/prompts/__init__.py | get_prompt |
| 发送前消息准备 | gptme/logmanager/manager.py | prepare_messages |
| 消息数据模型 | gptme/message.py | Message / to_toml / from_toml |
| 会话日志与持久化 | gptme/logmanager/manager.py | Log / LogManager(load / append / write_jsonl) |
| 事件日志 | gptme/logmanager/eventlog.py | append_event(events.jsonl) |
| 钩子触发 | gptme/hooks/registry.py | trigger_hook |
| 钩子类型枚举 | gptme/hooks/types.py | HookType |
| 会话初始化 | gptme/init.py | init |