这是一个用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模型的集装箱GGUFGeorgi 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能力聚焦在文件系统操作上。它设计了一套严格的语法协议让模型在输出中嵌入操作指令比如toolwrite_file/tool pathoutput/hello.cpp/path content #include iostream int main() { std::cout Hello, Jarvis! std::endl; return 0; } /content引擎解析到这段标记后就会在output/目录下创建文件。为了安全路径被严格限制在output/内防止AI手滑删你系统文件。2.5 量化Quantization让大模型减肥原始的大模型权重通常是16位浮点数FP16一个7B模型就要占14GB内存。量化就是把它压缩成低精度数字Q4_K_M4位量化压缩率约1/4质量损失很小是最常用的甜点方案。Q8_08位量化压缩率1/2质量几乎无损适合对精度要求高的场景。这个项目支持任何GGUF格式的模型所以你完全可以根据你的显存大小选择不同量化级别的模型。2.6 Docker与静态链接部署的终极形态项目用Docker做最终交付而且采用了多阶段构建静态链接Builder阶段在一个带编译环境的容器里用CMakeG编译出静态链接的二进制。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 CallingOpenAI风格或者JSON Schema因为简单可靠Function Calling需要模型原生支持而且要在分词层面做特殊处理。而用一个简单的文本标记协议如toolwrite_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 (检测到 toolwrite_file/tool) { │ │ │ │ 解析 path 和 content │ │ │ │ 安全检查路径是否在 output/ 目录内 │ │ │ │ 写入文件 → 返回成功提示给模型 │ │ │ │ } │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘4.2 核心模块拆解模块A模型加载与初始化这是程序的发动机启动阶段。llama.cpp提供了一套C API典型的初始化流程如下// 1. 初始化模型参数llama_model_params model_paramsllama_model_default_params();model_params.n_gpu_layers99;// 尽可能把层放到GPU上// 2. 加载GGUF模型llama_model*modelllama_load_model_from_file(model.gguf,model_params);// 3. 初始化上下文参数llama_context_params ctx_paramsllama_context_default_params();ctx_params.n_ctx4096;// 上下文窗口大小ctx_params.n_batch512;// 一次处理多少token// 4. 创建推理上下文llama_context*ctxllama_new_context_with_model(model,ctx_params);// 5. 初始化采样器控制输出的随机性llama_sampler*samplerllama_sampler_chain_init(llama_sampler_chain_default_params());llama_sampler_chain_add(sampler,llama_sampler_init_top_k(40));// Top-K40llama_sampler_chain_add(sampler,llama_sampler_init_temp(0.8));// 温度0.8llama_sampler_chain_add(sampler,llama_sampler_init_dist(1234));// 随机种子关键点n_gpu_layers决定多少层放到GPU上。如果显存够设成99实际是全部如果显存不够减少这个值剩下的层在CPU上跑。模块BRAG知识库加载这是项目的记忆植入阶段。逻辑非常简单直接std::stringbuild_rag_prompt(conststd::stringdata_dir){std::string knowledge;// 遍历 data/ 目录下的所有 .txt 文件for(constautoentry:std::filesystem::directory_iterator(data_dir)){if(entry.path().extension().txt){std::ifstreamfile(entry.path());std::stringcontent((std::istreambuf_iteratorchar(file)),std::istreambuf_iteratorchar());// 拼接文件名和内容knowledge[文件: entry.path().filename().string()]\\n;knowledgecontent\\n\\n;}}// 组装成System Promptreturn你是Jarvis一个本地AI助手。以下是你的知识库\\n---\\nknowledge---\\n请基于以上知识回答用户问题。;}设计亮点没有引入任何外部依赖如向量数据库、Embedding模型纯C标准库搞定。启动时一次性读取运行时零开销。模块C推理主循环这是项目的思考过程也是llama.cpp最核心的使用方式// 组装完整的对话PromptSystem User Assistant前缀std::string full_promptsystem_prompt\\n\\nUser: user_input\\nAssistant: ;// 分词把字符串转成token数组std::vectorllama_tokentokens;// ... 调用 llama_tokenize 进行分词 ...// 把token输入到模型llama_decode(ctx,llama_batch_get_one(tokens.data(),tokens.size()));// 生成循环std::string response;for(inti0;imax_tokens;i){// 采样得到下一个tokenllama_token new_tokenllama_sampler_sample(sampler,ctx,-1);// 如果生成结束标记跳出if(llama_token_is_eog(model,new_token))break;// 把token转回字符串输出charbuf[256];intnllama_token_to_piece(model,new_token,buf,sizeof(buf),0,true);std::stringpiece(buf,n);responsepiece;std::coutpiecestd::flush;// 流式输出// 把这个新token喂回模型继续预测下一个llama_decode(ctx,llama_batch_get_one(new_token,1));}关键点llama_decode是前向传播函数每次调用都会更新KV缓存Key-Value Cache这样模型就能记住前面的对话内容。模块DAgentic工具解析与执行这是项目的动手能力让AI从说话变成做事voidprocess_agentic_tools(conststd::stringresponse){// 查找工具调用标记size_t tool_posresponse.find(toolwrite_file/tool);if(tool_posstd::string::npos)return;// 提取路径size_t path_startresponse.find(path,tool_pos);size_t path_endresponse.find(/path,path_start);std::string filepathresponse.substr(path_start6,path_end-path_start-6);// 安全检查确保路径在 output/ 目录下if(filepath.find(..)!std::string::npos||filepath.find(/output/)!0){std::cerr非法路径拒绝执行std::endl;return;}// 提取内容size_t content_startresponse.find(content,path_end);size_t content_endresponse.find(/content,content_start);std::string contentresponse.substr(content_start9,content_end-content_start-9);// 写入文件std::ofstreamout(filepath);outcontent;out.close();std::cout[系统] 已创建文件: filepathstd::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.cppIf 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-GGUFQwen2.5-7B-Instruct Q4_K_M~4.7GB中文更强代码能力好ModelScope 搜Qwen2.5-7B-Instruct-GGUFPhi-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/Linuxmkdir-p~/Desktop/Jarvis_Output# AI写文件会出现在这里mkdir-p~/Desktop/My_Knowledge# 你的文档扔这里# Windows (PowerShell)mkdir$env:USERPROFILE\Desktop\Jarvis_Outputmkdir$env:USERPROFILE\Desktop\My_Knowledge往My_Knowledge里丢几个.txt文件测试比如建一个我的简历.txt姓名张三 技能C、Python、Docker 爱好折腾本地大模型第3步一条命令启动dockerrun-it--rm\-eTZAsia/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:latestWindows用户注意把路径里的~换成完整路径比如C:\Users\你的用户名\Desktop\...并且用 PowerShell 运行或者把反斜杠\改成双反斜杠\\。第4步测试对话启动成功后你会看到一个命令行交互界面。试试这几类问题测试1基础对话User: 你好你是谁→ 应该能正常自我介绍提到它是 Jarvis 助手。测试2RAG知识库关键测试User: 根据我的简历我有什么技能→ 如果 RAG 生效它会提到张三、“C”、Python这些你写在我的简历.txt里的内容。如果没提到说明 data/ 挂载有问题。测试3Agentic文件操作核心测试User: 帮我写一个Python的Hello World程序保存到文件→ 观察输出如果看到类似toolwrite_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架构检索增强生成、上下文注入、向量检索本项目未用用全文注入方案实现轻量级RAGAgentic 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【程序猿编码】