Golang封装llama.cpp实现AI工具包

内容分享3小时前发布
4 0 0

这是一份专门为 Cursor (使用 Claude-3.5-Sonnet 或 GPT-4o) 设计的详细执行计划。

要让 Cursor 高效完成这个 CGO 项目,关键策略是:分步投喂上下文。不要尝试一次性让它写完,由于 CGO 涉及 C++ 编译环境,Cursor 容易产生幻觉。

请按照以下步骤,依次将 【提示词 (Prompt)】 复制给 Cursor 的对话框(推荐使用 Cmd+I Composer 模式或 Chat 模式)。


项目预设信息 (第一复制这段发给 Cursor)

Context for Project:

我正在开发一个名为 my/go-llama-engine 的 Golang 项目。

目标:创建一个 Golang 工具包(SDK),通过 CGO 封装 llama.cpp 的核心功能。

核心需求:

它可以被其他 Go 项目引用。

支持加载 GGUF 格式模型。

支持通过切换 GGUF 文件路径来热更新模型。

提供简单的 Predict(prompt) 和 Stream(prompt) 接口。技术栈:Golang 1.22+, CGO, llama.cpp (作为子模块或库)。


第一阶段:环境搭建与库编译 (Phase 1: Setup)

目的:确保目录结构正确,且 llama.cpp 的 C++ 库文件编译成功。

步骤 1.1:初始化项目结构

【给 Cursor 的提示词】:

Plaintext

请帮我初始化项目目录结构。我需要以下文件结构:
- /llama.cpp (我不直接修改它,作为 submodule 或 clone 下来的源码目录)
- /pkg/binding (存放 CGO 绑定代码,处理 C <-> Go 类型转换)
- /pkg/engine (存放高层 Go 逻辑,如 Model 加载管理)
- /cmd/example (存放测试用的 main.go)
- go.mod (module name: github.com/lichv/go-llama-engine)

请执行 shell 命令创建目录并初始化 go mod

步骤 1.2:编译 C++ 动态库 (这一步提议你手动辅助 Cursor 确认)

【给 Cursor 的提示词】:

Plaintext

目前我们需要编译 llama.cpp 以生成动态链接库。
请注意:当前环境是 [MacOS/Linux/Windows,请修改此处为你的实际系统]。
请给出具体的 shell 命令,在 `llama.cpp` 目录下使用 cmake 编译出 `libllama` 动态库(.so 或 .dylib 或 .dll)。
编译完成后,请告知我如何将生成的库文件和头文件(llama.h, ggml.h 等)移动到我的项目根目录下的 `/lib``/include` 文件夹中,以便 CGO 引用。

(注意:Cursor 给出的命令运行后,请务必检查 /lib 目录下是否有生成好的库文件)


第二阶段:编写底层 CGO 绑定 (Phase 2: Low-Level Binding)

目的:打通 Go 和 C 的通信桥梁。这是最难的一步,需要 Cursor 读取头文件。

步骤 2.1:生成 CGO 桥接代码

【给 Cursor 的提示词】(请先在 Cursor 中打开/引用 include/llama.h 文件,让它看到内容):

Plaintext

@include/llama.h
参考上述 llama.h 头文件。请在 `/pkg/binding/binding.go` 中编写 CGO 代码。
你需要做以下几件事:
1. 配置 #cgo CFLAGS 和 LDFLAGS,指向项目根目录的 /include 和 /lib。
2. 定义一个 Go 结构体 `LLamaBinding`3. 封装 `llama_load_model_from_file``llama_new_context_with_model` 函数。
4. 确保处理好 C String 和 Go String 的转换,以及 C 指针的内存释放(Free)。

请只生成最基础的加载模型和释放内存的代码,先不要做推理逻辑。

步骤 2.2:实现 Tokenizer 和推理基础

【给 Cursor 的提示词】:

Plaintext

继续完善 `/pkg/binding/binding.go`。
我需要实现文本推理的基础功能。请添加以下 CGO 函数的封装:
1. `Tokenize(text string) []int`:将文本转为 token id 数组。
2. `Eval(tokens []int)`:调用 `llama_decode` 或相关函数进行推理。
3. `Sample()`:从 logits 中采样下一个 token id。
4. `TokenToPiece(token int) string`:将 token id 转回文本。

请确保代码中包含必要的 unsafe.Pointer 转换,并处理好错误返回值。

第三阶段:封装高层逻辑 (Phase 3: High-Level Engine)

目的:让调用者不需要关心 C 指针,只关心业务逻辑。

步骤 3.1:实现 Engine 结构体与模型加载

【给 Cursor 的提示词】:

Plaintext

目前编写 `/pkg/engine/engine.go`。
这是一个纯 Go 的封装层。
1. 定义 `Engine` 结构体,内部持有 `binding.LLamaBinding` 的实例。
2. 实现 `NewEngine(modelPath string) (*Engine, error)`3. 实现 `Engine.ReloadModel(newPath string) error`,这个方法需要先释放旧模型的 C 内存,然后加载新模型,实现热更新。

步骤 3.2:实现对话与流式输出

【给 Cursor 的提示词】:

Plaintext

在 `/pkg/engine/chat.go` 中实现推理逻辑。
1. 实现 `Predict(prompt string, options ...Option) (string, error)`:输入提示词,返回完整回复。
2. 实现 `Stream(prompt string) (<-chan string, error)`:实现流式输出,利用 Go channel 逐步返回生成的字符。
3. 也就是在循环中调用 binding 层的 Eval 和 Sample,直到遇到 EOS token。

第四阶段:测试与验证 (Phase 4: Verification)

目的:验证一切是否工作正常。

步骤 4.1:编写测试程序

【给 Cursor 的提示词】:

Plaintext

请在 `/cmd/example/main.go` 中编写一个完整的调用示例。
1. 初始化 Engine,加载一个本地的 GGUF 模型(路径通过命令行参数传入)。
2. 打印 "Model loaded successfully".
3. 调用 `Stream` 方法,输入 "Hello, explain Golang to me.",并将结果实时打印到控制台。
4. 模拟一次模型热更新:5秒后,调用 `ReloadModel` 重新加载模型(或加载另一个)。

步骤 4.2:运行与调试 (Debug 助手)

(如果运行时报错 “symbol not found” 或 “image not found”)

【给 Cursor 的提示词】:

Plaintext

我在运行的时候遇到了报错:[粘贴你的报错信息]。
这似乎是动态链接库路径的问题。
请帮我写一个 Makefile,包含 `run` 指令,确保在运行 go run 之前,设置好 `LD_LIBRARY_PATH` (Linux) 或 `DYLD_LIBRARY_PATH` (Mac),指向项目的 /lib 目录。

关键注意事项 (给开发者的 Tips)

  1. 文件路径:Cursor 有时会搞混相对路径。在 CGO 的 #cgo LDFLAGS 中,最好使用 ${SRCDIR} 宏来指定库文件的路径,这样更稳健。
  2. 硬件加速
  3. 如果你是 Mac M1/M2/M3:告知 Cursor 在编译 cmake 时必须加 -DLLAMA_METAL=ON。
  4. 如果你是 Nvidia 显卡:告知 Cursor 加 -DLLAMA_CUBLAS=ON。
  5. 如果不加这些,模型跑在 CPU 上会超级慢。
  6. 模型获取:你需要自己去 HuggingFace 下载一个 GGUF 文件(推荐 Qwen2.5-1.5B-Instruct-GGUF 这种小模型先用来测试,才几百兆,跑得快)。

你可以目前打开 Cursor,从 第一阶段 的提示词开始操作。如果有某一步报错,请把报错贴给我,我来调整下一条指令。

© 版权声明

相关文章

暂无评论

none
暂无评论...