徐仁康首页项目文章标签关于

agent4cpp

独立开发 · 2026

从零手写的 C++ Agent 框架。把工具调用、LLM 抽象和推理循环拆成互不依赖的几层。

C++17CMakelibcurlLLM 工具调用

源码:https://github.com/XX1mc1XX/agent4cpp

一个不依赖任何 Web 框架、不依赖 Python 的 C++ Agent 运行时,面向工业桌面软件——现场机器上装不了 Python 环境,但需要让大模型能操作本机设备。

三件事:让模型能调用本机能力(工具注册 + JSON Schema 参数)、把模型调用抽成可换的接口(Mock / OpenAI 兼容 / 本地模型)、跑一个会收敛的推理循环(每轮问模型 → 执行工具 → 结果回灌)。

规模:3,604 行 C++(src 2,054 + include 769 + tests 465 + examples 316)。

分层

层 职责 换掉它意味着
Status 统一错误码,跨层传递失败原因 —
Tool / ToolRegistry 工具描述(名字、用途、参数 Schema)+ 登记处 增加一类工具
ILLMClient 模型调用接口 从 Mock 换成真模型、或换服务商
IHttpClient HTTP 传输接口 从 libcurl 换成别的库
IKnowledgeStore 本地资料检索接口 从关键词检索换成向量检索
Agent 推理循环、消息记录、状态 —

每一层都只依赖上一层的抽象,不依赖具体实现。

三处值得说的设计

同一个套路出现了三次

ILLMClient、IHttpClient、IKnowledgeStore——三个能力各自一个纯虚接口,各自有实现:

接口 实现
ILLMClient MockLLMClient / OpenAICompatibleLLMClient
IHttpClient CurlHttpClient
IKnowledgeStore InMemoryKnowledgeStore

模式一样:上层只依赖抽象,具体实现可换。这不是为了「面向对象好看」——Mock 客户端让整条链路能在没有网络、没有 API Key、不花钱的情况下跑通和写测试,这是实际收益。

循环的终止条件只看一个东西

// 模型这一轮没有要求调工具,就是它认为可以回答了
if (response.tool_calls.empty()) break;

不猜、不发散,只看 tool_calls 是不是空。配套两条兜底:

  • 步数上限:转到上限还没停就强制收敛,防止模型在两个工具之间来回横跳
  • 工具失败也塞回对话:失败信息以「可重试」的语义回灌给模型,让它自己纠正,而不是直接把异常抛给用户

知识库不上向量数据库

检索走关键词打分:查询分词后,每命中一个词加一分,取前 K 个。零依赖、完全离线、每一步都能解释「为什么检索到这段」。

代价也很实在:同义词不认、中文分词粗糙。而且代码里有个真实缺陷——切块按字节切,中文会被切成非法 UTF-8。

这件事单独写了一篇:为什么我没给这个 Agent 接向量数据库

测试

465 行测试,覆盖跨层的纯逻辑部分。MockLLMClient 让整条 Agent 链路可以在没有网络的情况下跑断言——测试里能写「给它这个工具调用序列,它应该在第几轮停」。

相关文章

← 返回项目列表