『7x24小时有问必答』
这是一个用C++从头写的本地大模型推理引擎,不依赖Python,能读你的文档(RAG),还能帮你写代码、存文件(Agentic工具),一条Docker命令就能跑起来。

一、先搞明白:我们到底在造什么?

1.1 项目定位

你可以把它理解成一个**"纯血C++版的本地的ChatGPT+文件助手"**。但它有几个特别之处:
不依赖Python:现在很多本地大模型方案(比如Ollama、llama-cpp-python)底层虽然是C++,但外层都包了一层Python或者Go。这个项目是直接用C++调用llama.cpp的C API,省掉了Python解释器的开销。
自带"记忆力"(RAG):你把个人简历、公司文档、笔记往data/文件夹一扔,它启动时自动读进去,聊天时就能引用这些内容。
自带"动手能力"(Agentic):它不仅能聊天,还能按照你教的格式,在电脑上创建文件、写代码、保存日志——而且被关在output/目录里,不会乱动你系统。
Docker一键部署:编译好的静态二进制丢进容器,映射一个文件夹就能跑,连编译环境都不需要。

1.2 为什么不用Python?

很多人问:用Python不是更简单吗?
道理很简单:Python是胶水语言,C++是发动机语言。推理大模型时,真正的计算都在C++层(矩阵乘法、采样、内存管理),Python只是负责"传话"。但如果你的场景很固定——比如就是本地跑一个模型、读几个文档、偶尔写个文件——那Python这层"传话"反而成了累赘:
启动慢(要加载Python解释器)
内存占用多(Python对象头就几十字节)
部署麻烦(要管Python版本、依赖库、虚拟环境)
所以这个项目选择直接用C++写死循环,模型加载、分词、采样、文件I/O全部自己管,干净利落。

---

二、核心知识点扫盲

在讲代码之前,先快速过一遍这个项目涉及的关键概念。搞懂这些,后面的设计思路你就都能看明白了。

2.1 llama.cpp:大模型的"C++心脏"

llama.cpp是Georgi Gerganov写的一个开源项目,简单说就是把Meta的LLaMA模型用纯C++重新实现了一遍。它的核心优势:
无依赖:不需要PyTorch、CUDA Toolkit这些庞然大物,一个二进制文件就能跑。
量化压缩:能把模型的16位浮点数权重压到4位、甚至2位,让70B的大模型在消费级显卡甚至CPU上跑起来。
跨平台:x86、ARM、Mac、树莓派都能跑,甚至能编译成WebAssembly在浏览器里跑。
这个项目就是站在llama.cpp的肩膀上,直接调用它的C API来完成推理。

2.2 GGUF:模型的"集装箱"

GGUF(Georgi Gerganov Unified Format)是llama.cpp专用的一种模型格式。你可以把它想象成模型的集装箱:把权重、分词器、超参数、元数据全部打包成一个.gguf文件。
好处很明显:
一个文件就是全部,不用像PyTorch模型那样拖家带口(config.json、tokenizer.json、一堆bin文件)。
支持量化,文件体积能缩小到原来的1/4甚至1/8。
加载快,因为内存映射(mmap)可以直接把文件映射进内存,不用全部读到RAM里。

2.3 RAG:给AI装一个"外接硬盘"

RAG全称Retrieval-Augmented Generation(检索增强生成)。这个概念现在很火,但本质很简单:
大模型的知识只截止到训练数据,RAG就是让它能实时查"外接硬盘"里的文档,再回答你。
具体到这个项目,它的RAG实现非常"原生":启动时扫描data/目录,把所有.txt文件读出来,拼接成一段系统提示词(System Prompt),塞给模型。比如:
你是Jarvis,一个本地AI助手。以下是你的知识库:

---

[文件1: 我的简历.txt]

姓名:张三,10年C++开发经验...

[文件2: 项目文档.txt]

本项目使用CMake构建...

---

请基于以上知识回答用户问题。

这种方式虽然简单(没有向量数据库、没有语义检索),但对于个人本地场景完全够用——你往data/里扔十几个文档,总字数可能也就几万字,直接全塞进上下文窗口就行。省去了 embedding 模型、向量数据库这一整套复杂架构。

2.4 Agentic Tool Use:让AI从"动嘴"到"动手"

Agentic(代理化)是2025-2026年AI领域的大趋势。简单说就是:不仅让AI生成文字,还要让它能调用工具、操作外部环境。
这个项目的Agentic能力聚焦在文件系统操作上。它设计了一套严格的语法协议,让模型在输出中嵌入"操作指令",比如:
<tool>write_file</tool>

<path>output/hello.cpp</path>

<content>

include  <iostream>

int  main() {

      std::cout <<  "Hello, Jarvis!"  << std::endl;

       return  0;

}

</iostream></content>

引擎解析到这段标记后,就会在output/目录下创建文件。为了安全,路径被严格限制在output/内,防止AI"手滑"删你系统文件。

2.5 量化(Quantization):让大模型"减肥"

原始的大模型权重通常是16位浮点数(FP16),一个7B模型就要占14GB内存。量化就是把它压缩成低精度数字:
Q4_K_M:4位量化,压缩率约1/4,质量损失很小,是最常用的"甜点"方案。
Q8_0:8位量化,压缩率1/2,质量几乎无损,适合对精度要求高的场景。
这个项目支持任何GGUF格式的模型,所以你完全可以根据你的显存大小选择不同量化级别的模型。

2.6 Docker与静态链接:部署的"终极形态"

项目用Docker做最终交付,而且采用了多阶段构建+静态链接:
Builder阶段:在一个带编译环境的容器里,用CMake+G++编译出静态链接的二进制。
Runtime阶段:用一个极简的Linux基础镜像(比如Alpine或者scratch),只把静态二进制和必要的运行时文件拷进去。
这样做的好处是最终镜像极小,而且不依赖宿主机的任何库,真正做到"一次编译,到处运行"。

---

三、设计思路:为什么要这么造?

理解了上面的知识点,现在来看看这个项目的整体架构设计。我把它总结成**"三层设计"**。

3.1 第一层:推理层(C++原生)

设计决策:直接调用llama.cpp C API,不绕Python。
原因前面说了,为了性能和部署简洁。但这一层的设计有几个关键点:
内存自己管:C++没有Python的垃圾回收,模型权重、KV缓存、上下文内存都需要手动分配和释放。项目用RAII模式(资源获取即初始化)封装了llama_model和llama_context的生命周期,防止内存泄漏。
采样策略可配置:温度(Temperature)、Top-K、Top-P这些采样参数直接影响生成文本的"随机性"。项目把这些参数暴露成配置项,让用户可以调整AI的"性格"。
流式输出:不是等模型全部生成完再显示,而是每生成一个token就输出,这样用户体验更接近ChatGPT的打字机效果。

3.2 第二层:知识层(原生RAG)

设计决策:不用向量数据库,直接文本拼接进System Prompt。
这个决策可能看起来"土",但其实是针对本地个人场景的精准取舍:
个人文档通常不大:你的简历、几篇笔记、一些代码文档,加起来可能也就几千到几万token。而现在的模型(如Llama-3-8B)上下文窗口有8K甚至128K,完全装得下。
省去Embedding和向量检索的复杂度:不用装Sentence-Transformers、不用FAISS/Chroma、不用算余弦相似度。启动时读一遍文件,拼成字符串,完事。
检索精度反而高:因为所有文档都在上下文里,模型自己就能"看到"全部内容,回答时不会漏掉关键信息(而向量检索有时会把关键段落漏掉)。
当然,这种方案的局限也很明显:如果文档超过上下文窗口,就需要更复杂的分块和检索策略。但对于个人本地使用,这是性价比最高的方案。

3.3 第三层:工具层(Agentic文件操作)

设计决策:用XML-like标记协议,让模型输出结构化指令。
为什么不用Function Calling(OpenAI风格)或者JSON Schema?因为:
简单可靠:Function Calling需要模型原生支持,而且要在分词层面做特殊处理。而用一个简单的文本标记协议(如<tool>write_file</tool>),任何指令微调过的模型都能理解。
易于解析:C++里解析XML比解析JSON稍微麻烦点,但用简单的字符串查找(find/substr)就能搞定,不需要引入第三方库。
安全可控:通过正则或路径检查,确保所有文件操作都被限制在output/目录内。

---

四、代码实现原理与流程图

好了,知识点和设计思路都讲完了,现在来看看代码到底是怎么跑的。下面的流程图和伪代码是基于项目架构和llama.cpp标准API的合理推断,帮你理解核心逻辑。

4.1 整体架构流程图

┌─────────────────────────────────────────────────────────────┐

│                                    启动阶段                                                │

│   1. 解析命令行参数(模型路径、温度、上下文长度等)                     │

│   2. 扫描 data/ 目录,读取所有 .txt 文件                                    │

│   3. 拼接 RAG 知识库文本 → 生成 System Prompt                           │

│   4. 调用 llama.cpp API 加载 GGUF 模型                                       │

│   5. 初始化采样器(Sampler)和 KV 缓存                                        │

└─────────────────────────────────────────────────────────────┘

                                             │

                                             ▼

┌─────────────────────────────────────────────────────────────┐

│                                    交互循环                                                │

│   ┌─────────────┐      ┌─────────────┐      ┌─────────────────┐   │

│   │ 用户输入      │───│ 组装 Prompt │───│ Tokenize(分词) │   │

│   └─────────────┘      └─────────────┘      └─────────────────┘   │

│                                             │

│                                             ▼

│   ┌─────────────────────────────────────────────────────────┐   │

│   │ 推理循环(Decode)                                                         │   │

│   │   while  (token != EOS) {                                                 │   │

│   │      1. llama_decode()   // 前向传播,预测下一个token          │   │

│   │      2. llama_sampler_sample() // 采样(Top-K/Temp)          │   │

│   │      3. 输出token到屏幕                                                      │   │

│   │      4. 检查是否包含工具调用标记                                          │   │

│   │   }                                                                                 │   │

│   └─────────────────────────────────────────────────────────┘   │

│                                             │

│                                             ▼

│   ┌─────────────────────────────────────────────────────────┐   │

│   │ 后处理阶段                                                                      │   │

│   │   if  (检测到 <tool>write_file</tool>) {                            │   │

│   │      解析 <path> 和 <content>                                             │   │

│   │      安全检查:路径是否在 output/ 目录内?                            │   │

│   │      写入文件 → 返回成功提示给模型                                     │   │

│   │   }                                                                                 │   │

│   └─────────────────────────────────────────────────────────┘   │

└─────────────────────────────────────────────────────────────┘

4.2 核心模块拆解

模块A:模型加载与初始化

这是程序的"发动机启动"阶段。llama.cpp提供了一套C API,典型的初始化流程如下:
// 1. 初始化模型参数

llama_model_params model_params = llama_model_default_params();

model_params.n_gpu_layers =  99;   // 尽可能把层放到GPU上

// 2. 加载GGUF模型

llama_model* model = llama_load_model_from_file("model.gguf", model_params);

// 3. 初始化上下文参数

llama_context_params ctx_params = llama_context_default_params();

ctx_params.n_ctx =  4096;            // 上下文窗口大小

ctx_params.n_batch =  512;           // 一次处理多少token

// 4. 创建推理上下文

llama_context* ctx = llama_new_context_with_model(model, ctx_params);

// 5. 初始化采样器(控制输出的随机性)

llama_sampler* sampler = llama_sampler_chain_init(llama_sampler_chain_default_params());

llama_sampler_chain_add(sampler, llama_sampler_init_top_k(40));        // Top-K=40

llama_sampler_chain_add(sampler, llama_sampler_init_temp(0.8));        // 温度=0.8

llama_sampler_chain_add(sampler, llama_sampler_init_dist(1234));     // 随机种子

关键点:n_gpu_layers决定多少层放到GPU上。如果显存够,设成99(实际是全部);如果显存不够,减少这个值,剩下的层在CPU上跑。

模块B:RAG知识库加载

这是项目的"记忆植入"阶段。逻辑非常简单直接:
std::string  build_rag_prompt(const  std::string& data_dir)  {

       std::string  knowledge;

      

       // 遍历 data/ 目录下的所有 .txt 文件

       for  (const  auto& entry :  std::filesystem::directory_iterator(data_dir)) {

             if  (entry.path().extension() ==  ".txt") {

                   std::ifstream  file(entry.path());

                   std::string  content((std::istreambuf_iterator<char>(file)),

                                                     std::istreambuf_iterator<char>())
;

                  

                   // 拼接文件名和内容

                  knowledge +=  "[文件: "  + entry.path().filename().string() +  "]\\n";

                  knowledge += content +  "\\n\\n";

            }

      }

      

       // 组装成System Prompt

       return  "你是Jarvis,一个本地AI助手。以下是你的知识库:\\n---\\n"  

                 + knowledge +  "---\\n请基于以上知识回答用户问题。";

}

设计亮点:没有引入任何外部依赖(如向量数据库、Embedding模型),纯C++标准库搞定。启动时一次性读取,运行时零开销。

模块C:推理主循环

这是项目的"思考过程",也是llama.cpp最核心的使用方式:
// 组装完整的对话Prompt(System + User + Assistant前缀)

std::string  full_prompt = system_prompt +  "\\n\\nUser: "  + user_input +  "\\nAssistant: ";

// 分词:把字符串转成token数组

std::vector<llama_token> tokens;

// ... 调用 llama_tokenize 进行分词 ...

// 把token输入到模型

llama_decode(ctx, llama_batch_get_one(tokens.data(), tokens.size()));

// 生成循环

std::string  response;

for  (int  i =  0; i < max_tokens; i++) {

       // 采样得到下一个token

      llama_token new_token = llama_sampler_sample(sampler, ctx,  -1);

      

       // 如果生成结束标记,跳出

       if  (llama_token_is_eog(model, new_token))  break;

      

       // 把token转回字符串,输出

       char  buf[256];

       int  n = llama_token_to_piece(model, new_token, buf,  sizeof(buf),  0,  true);

       std::string  piece(buf, n);

      response += piece;

       std::cout  << piece <<  std::flush;   // 流式输出

      

       // 把这个新token喂回模型,继续预测下一个

      llama_decode(ctx, llama_batch_get_one(&new_token,  1));

}

关键点:llama_decode是前向传播函数,每次调用都会更新KV缓存(Key-Value Cache),这样模型就能"记住"前面的对话内容。

模块D:Agentic工具解析与执行

这是项目的"动手能力",让AI从说话变成做事:
void  process_agentic_tools(const  std::string& response)  {

       // 查找工具调用标记

       size_t  tool_pos = response.find("<tool>write_file</tool>");

       if  (tool_pos ==  std::string::npos)  return;

      

       // 提取路径

       size_t  path_start = response.find("<path>", tool_pos);

       size_t  path_end = response.find("</path>", path_start);

       std::string  filepath = response.substr(path_start +  6, path_end - path_start -  6);

      

       // 安全检查:确保路径在 output/ 目录下

       if  (filepath.find("..") !=  std::string::npos ||  

            filepath.find("/output/") !=  0) {

             std::cerr  <<  "非法路径,拒绝执行!"  <<  std::endl;

             return;

      }

      

       // 提取内容

       size_t  content_start = response.find("<content>", path_end);

       size_t  content_end = response.find("</content>", content_start);

       std::string  content = response.substr(content_start +  9, content_end - content_start -  9);

      

       // 写入文件

       std::ofstream  out(filepath);

      out << content;

      out.close();

      

       std::cout  <<  "[系统] 已创建文件: "  << filepath <<  std::endl;

}

安全设计:路径检查是重中之重。通过禁止..和强制前缀/output/,确保AI只能在自己的"沙盒"里操作。

4.3 数据流全景图

为了更直观地理解,下面是用户从输入到得到输出的完整数据流:
用户输入:  "帮我写个Hello World程序,保存到文件"

            │

            ▼

┌──────────────────┐

│   Prompt组装器       │

│   System: [RAG知识]│

│   User: 用户输入    │

│   Assistant:          │

└──────────────────┘

            │

            ▼

┌──────────────────┐

│   Tokenizer分词      │

│   "帮"→[token_123] │

│   "我"→[token_456] │

│   ...                     │

└──────────────────┘

            │

            ▼

┌──────────────────┐

│   llama_decode      │

│   前向传播×N次      │

│   KV缓存更新         │

└──────────────────┘

            │

            ▼

┌──────────────────┐

│   Sampler采样       │

│   Top-K筛选          │

│   Temperature随机   │

└──────────────────┘

            │

            ▼

流式输出:  "好的,我帮你写..."

            │

            ▼

┌──────────────────┐

│   Agentic解析器      │

│   检测到write_file │

│   提取路径+内容      │

│   安全检查通过       │

│   写入output/目录   │

└──────────────────┘

            │

            ▼

返回结果:  "已保存到 output/hello.cpp"

---

If you need the complete source code, please add the WeChat number (c17865354792)

五、Docker一键跑

第1步:准备模型文件

先去 HuggingFace 或 ModelScope 下载一个 GGUF 格式的模型。推荐这几个对新手友好的:
模型
大小
适用场景
下载地址
Llama-3-8B-Instruct Q4_K_M
~4.9GB
通用对话,中文也不错
HuggingFace  搜  Llama-3-8B-Instruct-GGUF
Qwen2.5-7B-Instruct Q4_K_M
~4.7GB
中文更强,代码能力好
ModelScope  搜  Qwen2.5-7B-Instruct-GGUF
Phi-4 Q4_K_M
~4.1GB
小钢炮,低显存也能跑
HuggingFace 搜  Phi-4-GGUF
  选模型的小技巧:文件名带  Q4_K_M  的是"甜点配置"——体积只有原版的1/4,但智商损失很小。显存8GB以下选这个,16GB以上可以试  Q8_0。
下载后把  .gguf  文件放到你找得到的地方,比如:
~/Downloads/llama-3-8b-instruct.Q4_K_M.gguf

第2步:准备知识库文件夹

在你电脑上建两个文件夹:
# macOS/Linux

mkdir -p ~/Desktop/Jarvis_Output         # AI写文件会出现在这里

mkdir -p ~/Desktop/My_Knowledge           # 你的文档扔这里

# Windows (PowerShell)

mkdir  "$env:USERPROFILE\Desktop\Jarvis_Output"

mkdir  "$env:USERPROFILE\Desktop\My_Knowledge"

往  My_Knowledge  里丢几个  .txt  文件测试,比如建一个  我的简历.txt:
姓名:张三

技能:C++、Python、Docker

爱好:折腾本地大模型

第3步:一条命令启动

docker run -it --rm \

   -e TZ="Asia/Shanghai"  \

   -v ~/Desktop/Jarvis_Output:/app/output \

   -v ~/Desktop/My_Knowledge:/app/data \

   -v ~/Downloads/llama-3-8b-instruct.Q4_K_M.gguf:/app/models/model.gguf \

   hitesh917/jarvis-cpp:latest

Windows用户注意:把路径里的  ~  换成完整路径,比如  C:\Users\你的用户名\Desktop\...,并且用 PowerShell 运行,或者把反斜杠  \  改成双反斜杠  \\。

第4步:测试对话

启动成功后,你会看到一个命令行交互界面。试试这几类问题:
测试1:基础对话
User: 你好,你是谁?

→ 应该能正常自我介绍,提到它是 Jarvis 助手。
测试2:RAG知识库(关键测试)
User: 根据我的简历,我有什么技能?

→ 如果 RAG 生效,它会提到"张三"、"C++"、"Python"这些你写在  我的简历.txt  里的内容。如果没提到,说明 data/ 挂载有问题。
测试3:Agentic文件操作(核心测试)
User: 帮我写一个Python的Hello World程序,保存到文件

→ 观察输出,如果看到类似  <tool>write_file</tool>  的标记,然后你去  ~/Desktop/Jarvis_Output  看,应该出现了一个  .py  文件。

六、知识要点总结

看完上面的内容,我们来总结一下这个项目涉及的核心知识领域:
知识领域
关键要点
项目中的应用
C++系统编程
RAII、内存管理、文件I/O、字符串处理
整个引擎的骨架,手动管理模型生命周期
LLM推理原理
自回归生成、KV缓存、采样策略(Top-K/Temp)
调用llama.cpp API完成token-by-token生成
模型量化
GGUF格式、Q4_K_M/Q8_0等量化方案
支持任意GGUF模型,让用户根据硬件选模型
RAG架构
检索增强生成、上下文注入、向量检索(本项目未用)
用"全文注入"方案实现轻量级RAG
Agentic AI
工具调用、Function Calling、安全沙盒
XML标记协议+路径限制实现文件操作
Docker容器化
多阶段构建、静态链接、Volume映射
一键部署,隔离运行环境
CMake构建系统
跨平台编译、依赖管理、编译优化
管理llama.cpp子模块和项目编译

---

总结

说实话,这个项目不是要做下一个Ollama或者vLLM——那些项目有团队维护、功能更全面。这个项目的价值在于**"极简主义"和"教育意义"**:
它证明了不用Python也能玩转LLM:对于很多C++开发者来说,这是很好的入门范例,展示了如何直接调用llama.cpp的C API。
它展示了"够用就好"的工程哲学:RAG不用向量数据库、Agentic不用复杂协议,在本地个人场景下,这种取舍是合理的。
它提供了完整的生产部署范本:从源码到Docker镜像,从本地调试到一键部署,这条路是通的。
如果你是个想深入理解LLM底层原理的开发者,或者你需要一个完全可控、不依赖外部服务、能操作本地文件的AI助手,这个项目值得一看。
Welcome to follow WeChat official account【程序猿编码】

---

</llama_token></content></path>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码