0.1-为什么用 C++ 写一个 AI Agent:整体架构与阅读路线

为什么用 C++ 写一个 AI Agent:整体架构与阅读路线

源码仓库:

ai-agent-cpp 是一个用 C++20 写成的 AI Agent,把「DeepSeek 模型调用」「工具调用循环」「A2A 协议对外服务」「MCP 工具扩展」四件事组装成一个可运行的服务。它由三个仓库协作完成:主项目做编排,A2A 库定通信,MCP 平台供工具。

这篇是专栏开篇,不深入任何模块,只做一件事:画出一张完整的地图------三个仓库怎么分工、一个请求怎么流动、源码该从哪里开始读。后面每一篇都会沿这张地图往下钻。

适合谁读:会 C++、调过 ChatGPT 或 DeepSeek 接口、但没亲手搭过 Agent 的工程师。需要能读懂 C++20(模板、RAII、线程)和基本的 HTTP/JSON;A2A、MCP 这些协议不用预先了解,遇到时会解释。

至于为什么不用现成框架、为什么选 C++,下一节展开。


1. 问题背景:为什么又要手写一个 Agent

市面上的 Agent 框架已经很多了。再手写一个,我先说清楚它解决的是什么问题,以及代价是什么。

1.1 一个具体的运行场景

设想我要的是一个本地终端 coding agent:

  • 在终端输入一句话,它流式输出;
  • 需要时它能读文件、改文件、跑 bash;
  • 能调用外部工具(MCP 插件);
  • 能作为 A2A 服务被其它 Agent 调用;
  • 长会话不能撞上下文窗口;
  • 关掉终端再打开,会话还在。

这些需求单独看都不难,难的是同时成立且可控。用现成框架通常能快速搭起来,但一旦要改协议细节、改上下文策略、改工具执行方式,就会不断和框架的抽象层对抗。

1.2 为什么是 C++

最主要的原因很朴素:我日常工作接触最多的就是 C++ 。用熟悉的语言,精力可以放在 Agent 本身的机制上,而不用将精力分给一门新语言。

在熟悉的基础上,C++20 也确实适合做这件事:

  • 单进程、单二进制、无运行时依赖:不需要 Python 虚拟环境,也不需要 Node 服务做胶水(Web 前端是可选附加)。
  • 对底层协议有完全控制:A2A 的 JSON-RPC 绑定、SSE 分帧、MCP 的 stdio 读写,都是自己实现的,出问题能直接定位。
  • 性能与并发可控:多会话并发、工具执行、日志轮转都在一个进程内,用线程和锁管理,不依赖事件循环的黑盒。
  • 作为学习载体:协议、网络、并发、持久化、编译构建,一个项目全都能覆盖。

代价也很明确:开发速度慢于脚本语言,字符串、JSON、HTTP 都得自己拼装或引依赖。所以这个项目的做法是只在不值得自己写的地方引依赖 :JSON 用单头文件 nlohmann/json,HTTP 用 cpp-httplib(header-only),SQLite 用官方 amalgamation,其余全部手写。

我没说 C++ 比别的语言更适合写 Agent。用 Python、Go、TypeScript 一样能写,很多时候还更快。选 C++ 更多是「我熟悉 + 想顺便练手」,谈不上技术选型上的胜负。


2. 三个仓库的分工

这三个仓库按三个方向拆开:做应用 、定协议 、供能力。

仓库 作用 源码量级(约)
ai-agent-cpp 应用层,把模型、工具、协议、前端组装成一个可运行的产品 C++ 6.5k 行 + Node 4k 行
a2a-protocol 实现 A2A v1.0,负责 Agent 之间的互相调用 C++ 5k 行
mcp-extension-platform 实现 MCP,负责工具的注册、发现与执行 C++ 5k 行 + Python 插件

三者之间是依赖关系:

javascript 复制代码
 ai-agent-cpp
   ├── 依赖 a2a-protocol   → 对外暴露 A2A 服务(HTTP/JSON-RPC/SSE)
   └── 通过 stdio 启动 mcp-extension-platform → 获得一堆可调用的工具
  • a2a-protocol 通过 Git submodule 引入(vendor/a2a-protocol),在 CMake 里作为子项目直接编译;
  • mcp-extension-platform 是独立的服务进程 ,ai-agent-cpp 用 stdio 与之通信,不把它编进自己的二进制。

这个区别很重要:协议库是编译期依赖,工具平台是运行期进程。前者可以看成"一个库",后者是一个要被启动的"外部系统"。


3. 一个请求的一生

这是整个专栏的主线。用户在浏览器或终端说一句话,它经历的完整路径如下。

3.1 组件架构

flowchart TB subgraph client[客户端] Browser[浏览器 Web UI] CLI[终端 CLI de] end subgraph app[ai-agent-cpp 进程] BFF[Node BFF<br/>web/node_frontend] GW[WebGateway<br/>REST + SSE] A2A[A2A AgentHttpServer<br/>JSON-RPC] RT[AgentRuntime<br/>历史 + 工具循环] DS[DeepSeekClient] TOOLS[CompositeToolProvider] LOCAL[LocalCodingTools<br/>read/write/edit/bash] BRIDGE[MCPClientBridge] CACHE[HistoryCache] META[SessionMetaStore] STORE[(SqliteTaskStore)] INST[ProjectInstructionsLoader<br/>AGENTS.md] LOG[Logger] end subgraph ext[外部] API[DeepSeek API] MCPSRV[mcp-extension-platform<br/>stdio JSON-RPC] end Browser --> BFF --> GW CLI --> GW GW --> RT A2A --> RT RT --> DS --> API RT --> TOOLS TOOLS --> LOCAL TOOLS --> BRIDGE --> MCPSRV RT --> INST RT --> CACHE GW --> META GW --> STORE RT --> STORE GW -.事件.-> Browser

3.2 时序:一次带工具调用的流式对话

sequenceDiagram participant U as 用户(CLI/浏览器) participant G as WebGateway participant R as AgentRuntime participant D as DeepSeekClient participant T as ToolProvider participant S as SQLite U->>G: POST /api/v1/chat/stream (message, session_id, cwd) G->>R: HandleMessageStreamingEvents(...) R->>R: 加载历史 + 注入 AGENTS.md system loop 直到模型不再请求工具 R->>D: ChatStream(messages, tools) D-->>R: SSE token / tool_calls R-->>G: token 事件 G-->>U: SSE 逐 token 推送 opt 模型发起 tool_calls R->>T: Call(tool_name, args) T-->>R: 工具结果 R-->>G: tool_start / tool_output / tool_end end end R->>S: Put(task.history) G-->>U: done

这条链路里有三个关键设计,后面会分别展开:

  1. 流式事件用结构化对象承载 (AgentEvent):token、工具开始/输出/结束、压缩事件都有明确类型,WebGateway 再转成 SSE 帧推给前端。
  2. 工具循环没有固定轮次上限。只要模型继续请求工具就继续执行,对齐主流的 agent loop 行为(这是演进后的结果,早期版本有上限)。
  3. 历史落库与内存缓存同时更新,避免每次请求全表扫描。

4. 核心特性速览

每条都能在源码里找到落点,后续各篇会逐一精读。

特性 关键实现文件 后续篇目
DeepSeek 同步 / SSE 流式 / tool_calls src/llm/deepseek_client.cpp 1.2
Web Gateway(REST + SSE) src/web/web_gateway.cpp 1.4
Agent 主循环与结构化事件 src/agent/agent_runtime.cpp、agent_event.hpp 1.3
零依赖 Node BFF 与终端 CLI web/node_frontend/、cli/ 1.5
内置 coding 工具 read/write/edit/bash src/tools/local_tools.cpp、run_command.cpp 2.2
MCP stdio 桥接 src/mcp/mcp_client_bridge.cpp 2.3
上下文压缩(token 计量 + 六段摘要) src/agent/compaction.cpp 2.4 / 2.5
会话持久化与内存缓存 src/agent/history_cache.cpp、session_meta_store.cpp 2.6
项目指令 AGENTS.md 加载 src/agent/project_instructions.cpp 2.7
零依赖日志(request id / 脱敏 / 轮转) src/log.cpp 2.8
A2A v1.0 协议实现 vendor/a2a-protocol/ 3.x
MCP 平台(传输 / 插件 / RAG) vendor/mcp-extension-platform/ 4.x

5. 源码导航:从哪开始读

5.1 主仓库目录

bash 复制代码
 ai-agent-cpp/
 ├── CMakeLists.txt              # 顶层构建:5 个静态库 + 主程序 + 测试
 ├── conf/
 │   ├── app.ini.example         # 配置模板([deepseek]/[mcp]/[logging]/[compaction]/[instructions])
 │   └── conf.ini                # 本地密钥配置(gitignore)
 ├── include/aiagent/            # 公共头文件
 │   ├── agent/                  # AgentRuntime / compaction / history_cache / instructions / session_meta
 │   ├── llm/                    # DeepSeek 客户端与类型
 │   ├── mcp/                    # MCP stdio 桥
 │   ├── tools/                  # 工具抽象与内置实现
 │   └── web/                    # Web Gateway
 ├── src/                        # 与 include/ 对应的实现 + main.cpp
 ├── tests/                      # C++ 测试(11 个 ctest 用例)
 ├── examples/                   # deepseek_chat 命令行示例
 ├── cli/                        # 终端 coding 客户端(Node,零依赖)
 ├── web/node_frontend/          # Node BFF + 静态前端
 ├── de                          # 一键启动器(守护后端 + CLI)
 ├── scripts/                    # install/uninstall de
 └── vendor/                     # 两个 submodule

CMake 里的目标划分很清晰,按依赖从底到顶:

复制代码
 aiagent_log    ← 日志,零依赖
 aiagent_llm    ← 配置 + DeepSeek 客户端(依赖 a2a)
 aiagent_agent  ← AgentRuntime / 工具 / 压缩 / 缓存 / 指令(依赖 llm)
 aiagent_mcp    ← MCP 桥(依赖 llm)
 aiagent_web    ← Web Gateway(依赖 agent)
 aiagent_app    ← 主程序,组装以上全部

5.2 两个子仓库

  • vendor/a2a-protocol/:core/(JSON-RPC、错误码、SSE、HTTP)→ models/(Part/Message/Task/AgentCard)→ server/(AgentServer、TaskManager、TaskStore、HTTP 服务端)→ client/(客户端与 Agent Card 发现)。
  • vendor/mcp-extension-platform/:src/server/(JSON-RPC 路由)→ src/transport/(Stdio/SSE/HttpStream)→ src/loader/(.so 与 .py 插件加载)→ src/bridge/(Python 子进程桥)→ src/rag/(语义检索)。

5.3 阅读入口

如果你想自己读代码,建议顺序是:

bash 复制代码
 src/main.cpp
   → include/aiagent/agent/agent_runtime.hpp
   → src/agent/agent_runtime.cpp
   → src/llm/deepseek_client.cpp
   → src/tools/local_tools.cpp
   → src/mcp/mcp_client_bridge.cpp
   → src/web/web_gateway.cpp

main.cpp 是整个项目的"组装说明书",所有模块在这里被 new 出来并互相连接。读懂了它,就知道每个模块的边界在哪。


6. 先跑起来

不深入细节,先把服务拉起来,确认环境没问题。

css 复制代码
 # 1. 拉取两个 submodule
 git submodule update --init --recursive
 ​
 # 2. 构建(配置阶段会自动下载 cpp-httplib 与 SQLite)
 cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
 cmake --build build -j
 ​
 # 3. 跑测试:不需要真实 API Key
 ctest --test-dir build --output-on-failure

实测输出(本机 GCC 11.4 / CMake 3.22):

erlang 复制代码
 100% tests passed, 0 tests failed out of 11

这三个步骤不需要任何 API Key:测试用本地 mock HTTP server 和 fake MCP 进程完成验证。只有当你要做真实对话时,才需要:

bash 复制代码
 cp conf/app.ini.example conf/conf.ini   # 填入真实的 DeepSeek api_key
 ./build/aiagent_app --config conf/conf.ini --db build/db/aiagent_tasks.db

启动后的输出(本机实测,模型名以 conf.ini 为准):

csharp 复制代码
 MCP disabled (binary not found)
 ai-agent-cpp listening on http://127.0.0.1:18080/
 Web Gateway: http://127.0.0.1:18081/
 Agent Card: http://127.0.0.1:18080/.well-known/agent-card.json
 Task DB: build/db/aiagent_tasks.db
 Press Ctrl+C to stop.

A2A 在 18080、Web Gateway 在 18081,默认端口与 db 路径来自 src/main.cpp 的 AppOptions。MCP disabled (binary not found) 是因为 vendor/mcp-extension-platform 还没构建:它的 MCP server 是独立进程,构建后重启即可加载。

  • 让de agent实现一个坦克大战
  • 坦克大战实现效果

7. 本专栏的阅读路线

按依赖关系和难度,专栏分五卷。本文属于卷零(导读),接下来:

复制代码
 卷零  导读            ← 你在这里
 卷一  主干:一次对话的全链路
 卷二  深水区:Agent Runtime 工程化
 卷三  协议:A2A 实现
 卷四  扩展:MCP 平台
 卷五  收尾:工程方法论与复盘

建议的阅读顺序:

  • 想快速理解整体:卷零 → 卷一 → 卷五。
  • 对 Agent 内部机制感兴趣:卷零 → 卷二(尤其上下文压缩)。
  • 对协议/分布式感兴趣:卷零 → 卷三 → 卷四。

完整选题清单见 99-系列导航.md。


8. 小结

三条要点:

  1. 写它的目的,是用一个 C++ 项目把 Agent 工程的关键环节完整走一遍:模型调用、工具循环、协议、持久化、可观测性。
  2. 三个仓库职责分明:主项目做编排,A2A 库定通信,MCP 平台供工具;前者是编译期库依赖,后者是运行期独立进程。
  3. 阅读入口是 src/main.cpp,它把所有模块连成一张图;这张图就是本专栏后续所有文章的坐标系。

下一篇会把第 6 节的启动过程拆开,讲清每一步在做什么、失败时怎么排查。

相关推荐
编程老船长2 小时前
模型中立——把大模型做成"可替换零件",而不是焊死在业务里
java·前端·后端
光影少年2 小时前
Redis + Node 如何支撑百万级并发
redis·后端·node.js
小白男神2 小时前
MySQL进阶学习六(InnoDB存储引擎)
后端·mysql
陈随易2 小时前
傻瓜式UX:ERP的致命糖衣
前端·后端·程序员
大白802 小时前
批量插入数据,怎么写 SQL 效率最高?
后端
Solis2 小时前
MVCC原理
后端·面试
Thneonl2 小时前
drain 卡了 40 分钟:PDB 才是节点维护的主语
后端·架构
百万蹄蹄向前冲2 小时前
妙!文档懒得写,就让AI 生成新员工学习指南
前端·后端·trae