尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从零搭建个人 LLM 工具链:本地推理、网关、Agent 与 RAG 实战

从零搭建个人 LLM 工具链:本地推理、网关、Agent 与 RAG 实战 最近接连看到好几条 “Show HN: LLM Tools that I made that I cant live without” 这类帖子发现很多 LLM 开发者并不是被某个“全能框架”拯救而是被一堆小而美的自建工具解放了。自己从去年开始接触 LLM 应用开发从纯调 API 到现在跑本地模型、搭 Agent、接 RAG中间踩了不少坑也沉淀下来一套日常开发离不开的工具集。这篇文章就把这套 LLM 工具链完整拆开讲选型思路、环境准备、核心代码、常见排错、工程建议。内容偏实战适合已经会一点 Python、想从“调 API 示例”走向“完整 LLM 应用”的开发者。1. LLM 开发到底缺什么工具1.1 问题的起点刚开始接触 LLM 开发时大部分人都经历过这几个阶段拿一个 OpenAI SDK、一个国产大模型 API跑通一个聊天接口觉得“这就结束了”。等需求变成“支持多个模型切换”“给模型加工具调用”“把私有知识库接进去”时发现原来的示例代码根本撑不住。线上调用失败、响应超时、模型输出乱跳想排查却只有控制台里的 Key-Value 日志。本质问题是LLM 应用不是“一个接口”而是一条完整链路。这条链路至少包含模型接入层本地推理、云端 API、模型路由。应用编排层Agent、工具调用、人工审批。知识增强层文档切分、Embedding、向量检索。可观测层日志、Token 统计、成本统计、效果评测。知识沉淀层让团队/个人把零散文档变成可复用的“AI 知识库”。1.2 LLM 工具要解决三类问题第一类接口太杂。OpenAI、Anthropic、国产模型、本地模型接口风格各不相同前端应用不想为每一家写适配。第二类上下文太脆弱。直接往 Prompt 里塞文档很快就把上下文窗口撑爆回答质量还不可控。第三类能力边界不清晰。模型不知道什么时候该查数据库、什么时候该搜索网页、什么时候该执行代码必须有工具层帮它决策。所以所谓“离不开的工具”本质上是在给模型搭一个稳定、可控、可观测的外围系统。1.3 这篇文章的路线图下面会从零开始搭建一套个人 LLM 工具链涵盖五类核心工具本地推理引擎统一 LLM 网关Agent 与 Function Calling 工具RAG 向量检索工具LLM Wiki 知识管理工具每一类都会给出选型思路、核心配置、代码示例和真实使用感受。文章最后还会补充 FP16、FP32、BF16 三个精度参数的选择方法这部分在本地部署时踩坑最多。2. 环境准备与版本说明2.1 硬件与操作系统本地推理推荐使用以下环境操作系统Ubuntu 22.04 或 macOSApple Silicon。显卡NVIDIA GPU 建议显存 8GB 以上Mac 建议内存 16GB 以上。内存建议 32GB 以上本地跑 7B 模型更从容。磁盘预留 20GB 以上用于存放模型文件。如果硬件资源不足也可以只保留“云端 API 网关”部分本文的代码结构不依赖本地模型。2.2 软件环境Python3.10 或以上。包管理pip 或 uv。代码 IDEVS Code、PyCharm 均可。Docker用于部署网关和向量库。版本建议Python 3.10 FastAPI / Uvicorn httpx / requests需要说明的是LLM 工具链的版本迭代非常快下面所有示例都不绑定某一个固定版本重点演示思路。你把代码放进自己的项目时需要根据实际版本调整。2.3 项目结构本文会围绕一个名叫llm-toolkit的工程来讲解目录结构如下llm-toolkit/ ├── gateway/ │ ├── main.py # LLM 网关统一 API 入口 │ └── routes.py # 模型路由配置 ├── local_engine/ │ └── run_ollama.py # 本地推理引擎调用示例 ├── agent/ │ ├── tool_register.py # 工具注册表 │ └── agent_loop.py # Agent 主循环 ├── rag/ │ ├── splitter.py # 文本切分 │ ├── embedder.py # 向量化 │ └── retriever.py # 检索 └── wiki/ └── wiki_builder.py # LLM Wiki 构建脚本这就是我日常做 LLM 项目的基础骨架。下面的内容会围绕这个骨架展开。3. 核心工具一本地推理引擎3.1 为什么需要一个本地推理引擎虽然云端 API 很方便但本地推理引擎几乎是 LLM 开发者的“必备调试工具”。原因有三个调试 Prompt 不用花钱随便试、随便刷。数据不出机器适合处理敏感的业务日志。它能和云端 API 形成互补简单任务走本地复杂任务走大模型。实际项目中先用本地小模型验证流程再切到云端大模型跑正式结果是性价比最高的开发方式。3.2 常见的本地推理引擎目前使用较多的两个方案Ollama安装简单命令友好适合个人开发与实验。llama.cpp底层性能强量化方案成熟适合追求极致性能和嵌入到服务端的场景。在 Mac 上Ollama 默认启用 Metal 加速体验比较流畅在 Linux 上可以配合 GPU 使用。如果你只是想快速把工具链跑起来Ollama 是最省力的选择。安装示例Ubuntucurl -fsSL https://ollama.com/install.sh | sh安装完成后拉取一个模型ollama pull qwen2.5:7b具体能拉哪些模型、用什么标签要以 Ollama 官方模型库为准这里只演示命令流程。3.3 用 Python 调用本地推理引擎启动 Ollama 服务后默认监听http://localhost:11434。可以直接用requests调用也可以安装官方 SDK。这里演示一个最基础的工具函数用 Python 调用本地聊天接口# 文件路径llm-toolkit/local_engine/run_ollama.py import requests def chat_with_local_model( prompt: str, model: str qwen2.5:7b, base_url: str http://localhost:11434, ) - str: 调用本地 Ollama 服务的对话接口。 参数说明 - prompt: 用户输入 - model: 模型名称需提前用 ollama pull 拉取 - base_url: Ollama 默认服务地址 payload { model: model, messages: [{role: user, content: prompt}], stream: False, } resp requests.post(f{base_url}/api/chat, jsonpayload, timeout120) resp.raise_for_status() return resp.json()[message][content] if __name__ __main__: answer chat_with_local_model( prompt用一句话说明向量化检索是什么, modelqwen2.5:7b, ) print(answer)运行方式python llm-toolkit/local_engine/run_ollama.py注意streamFalse的含义是关闭流式输出等待完整回答返回。实际项目里更推荐流式输出前端体验更好后面在最佳实践里再展开。3.4 本地推理引擎的选型建议只有 8GB 显存优先选 7B 量化模型。内存充足的 Mac可以跑 7B 到 14B 模型但要注意内存占用。需要生产级稳定性不建议直接暴露本地引擎给业务而是放在网关后面统一管理。4. 核心工具二统一 LLM 网关4.1 网关解决的问题当项目同时使用本地模型和多种云端模型时会遇到几个很现实的问题不同厂商接口格式不同调用方要写多个客户端。某个模型挂了没有自动降级策略。密钥散落在各个服务里安全审计困难。统一 LLM 网关就是让上层应用只面向一个 API由网关决定把请求路由到哪个模型、走哪家厂商、是否重试。4.2 OpenAI 兼容接口是事实标准现在大多数模型服务都提供了 OpenAI 兼容接口路径通常是/v1/chat/completions。这意味着网关可以设计成一个“透传 路由”的服务上层应用只认这个路径网关内部再转发到不同后端。4.3 用 FastAPI 实现一个轻量 LLM 网关下面是一个简化版网关核心功能是接收 OpenAI 兼容格式的请求。根据model字段路由到不同后端。返回统一响应格式。# 文件路径llm-toolkit/gateway/main.py from fastapi import FastAPI, HTTPException, Request import httpx app FastAPI(titleLLM Gateway) # 模型路由表根据 model 名称找到目标服务 MODEL_ROUTES { local: http://localhost:11434, remote: https://your-model-endpoint.example.com, } app.post(/v1/chat/completions) async def chat_completion(request: Request): body await request.json() model body.get(model, ) if model not in MODEL_ROUTES: raise HTTPException( status_code400, detailf未配置的模型路由: {model}, ) target MODEL_ROUTES[model] headers {} # 如果后端需要 API Key从环境变量读取避免硬编码 if model remote: api_key os.getenv(REMOTE_API_KEY, ) headers[Authorization] fBearer {api_key} async with httpx.AsyncClient() as client: resp await client.post( f{target}/v1/chat/completions, jsonbody, headersheaders, timeout120, ) resp.raise_for_status() return resp.json()启动网关cd llm-toolkit/gateway pip install fastapi uvicorn httpx uvicorn main:app --host 0.0.0.0 --port 8000上层应用只需要把 base_url 指向http://localhost:8000然后传入modellocal或modelremote。4.4 网关还能扩展什么上面的代码只是最基础的版本真实工程里网关还可以加请求日志记录每次调用的模型、Token 数、耗时。超时与重试针对不同模型配置不同超时时间。多密钥轮换来分散负载。加一层缓存相同请求直接命中缓存。网关是整套工具链里投入产出比最高的一层建议优先完善。5. 核心工具三Agent 与 Function Calling 工具5.1 Agent 工具的本质聊到 Agent、Function Calling先放下那些夸张的概念。从工程角度看就是三件事告诉模型有哪些函数可以调用。模型判断“这个请求需要调用函数”输出结构化调用指令。程序执行真实函数把结果回传模型模型继续生成。这套流程让模型从“只会输出文字”变成“能操作真实系统”。5.2 工具描述格式各家 API 的工具描述格式大同小异。下面是通用的示例描述一个“抓取网页内容”的工具{ type: function, function: { name: fetch_webpage, description: 根据 URL 抓取网页正文内容用于搜索引擎无法覆盖的信息源, parameters: { type: object, properties: { url: { type: string, description: 需要抓取的完整网页地址 } }, required: [url] } } }5.3 工具注册表为了避免代码里到处写 if-else建议用一个注册表来管理所有工具# 文件路径llm-toolkit/agent/tool_register.py import inspect class ToolRegistry: 工具注册表统一管理 Agent 可用工具。 def __init__(self): self._tools {} def register(self, func): 通过装饰器注册函数为可用工具 self._tools[func.__name__] func return func def call(self, name: str, args: dict): 执行工具 if name not in self._tools: raise ValueError(f未注册的工具: {name}) return self._tools[name](**args) def list(self): 返回所有工具的函数签名供 LLM 看到 result [] for name, func in self._tools.items(): result.append( { type: function, function: { name: name, description: inspect.getdoc(func) or , parameters: { type: object, properties: { k: {type: string} for k in inspect.signature(func).parameters.keys() }, }, }, } ) return result registry ToolRegistry() registry.register def fetch_webpage(url: str) - str: 根据 URL 抓取网页正文内容。 # 这里只展示骨架实际需要结合 requests BeautifulSoup 等库实现 return f网页 {url} 的正文内容主循环可以写成# 文件路径llm-toolkit/agent/agent_loop.py from tool_register import registry def run_agent_with_tool(user_message: str, model_chat_func): 简化版 Agent 主循环。 model_chat_func 是一个函数 tool_schemas: list messages: list - 返回模型的 JSON 响应 messages [{role: user, content: user_message}] # 第一轮把工具描述和用户消息一起发给模型 resp model_chat_func(tool_schemasregistry.list(), messagesmessages) # 如果模型没有要求调用工具直接返回文本 if not resp.get(tool_calls): return resp[content] # 如果模型要求调用工具执行工具并把结果回传 tool_calls resp[tool_calls] for call in tool_calls: result registry.call(call[name], call[arguments]) messages.append( { role: tool, name: call[name], content: str(result), } ) # 第二轮把工具结果交给模型生成最终回答 final_resp model_chat_func(tool_schemasregistry.list(), messagesmessages) return final_resp[content]这段代码不是某个 SDK 的完整实现而是 Agent 工具调用的通用流程核心是“模型小步决策、代码真实执行、结果回填再生成”。5.4 关于 MCP最近经常看到 MCPModel Context Protocol它其实是在做一件更标准化的事情把“工具描述 工具调用”抽成统一协议。这样不同 LLM 应用可以复用同一套工具服务不必每家 SDK 单独适配。如果你要写 MCP Client核心步骤仍是连接 MCP Server。拉取 Server 暴露的工具列表。把工具描述交给 LLM。由 LLM 决定调用哪个工具再经 Client 发回 Server。也就是说MCP 并没有改变 Agent 的基本循环它只是让工具的定义和传输更规范。日常开发中不用迷信 MCP先用注册表把工具管理好再考虑是否迁移到 MCP。6. 核心工具四RAG 向量化与知识库工具6.1 RAG 解决什么问题LLM 最常见的痛点是“不懂私域知识”。把资料直接塞进 Prompt 又塞不下于是有了 RAG 方案。RAG 全称是 Retrieval-Augmented Generation翻译过来是“检索增强生成”。逻辑很清晰先把私有文档切块、向量化存进向量库。用户提问时把问题也向量化。在向量库里检索最相似的内容。把检索结果拼进 Prompt让模型基于这些资料回答。6.2 Embedding 是什么Embedding 就是把文本变成一串数字向量。两个文本的向量在多维空间里越接近语义就越相似。所以 RAG 检索经常用余弦相似度来判断“问题”和“文档片段”是否相关。6.3 一个极简 RAG 流程先写文本切分工具# 文件路径llm-toolkit/rag/splitter.py def split_text(text: str, chunk_size: int 500, overlap: int 50): 把长文本切成块块之间保留重叠避免切断语义。 if len(text) chunk_size: return [text] chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) start end - overlap return chunks再写向量化与检索的骨架# 文件路径llm-toolkit/rag/embedder.py from typing import List def embed_texts(texts: List[str], embed_fn) - List[List[float]]: 把文本列表批量向量化。 embed_fn 是外部传入的向量化函数可以来自 - 本地 Embedding 模型 - 云端 Embedding API vectors [] for text in texts: vector embed_fn(text) vectors.append(vector) return vectors# 文件路径llm-toolkit/rag/retriever.py import math def cosine_similarity(vec_a: List[float], vec_b: List[float]) - float: 计算两个向量的余弦相似度值越接近 1 表示语义越相近。 dot sum(a * b for a, b in zip(vec_a, vec_b)) norm_a math.sqrt(sum(a * a for a in vec_a)) norm_b math.sqrt(sum(b * b for b in vec_b)) if norm_a 0 or norm_b 0: return 0.0 return dot / (norm_a * norm_b) def search(query_vector: List[float], doc_vectors: List[List[float]], top_k: int 5): 在文档向量里检索最相似的前 k 个片段。 scored [] for idx, vec in enumerate(doc_vectors): score cosine_similarity(query_vector, vec) scored.append((score, idx)) scored.sort(reverseTrue) return scored[:top_k]6.4 向量数据库怎么选日常开发有几个选择数据量小、刚起步直接用numpy或sqlite存向量都行。数据量中等、需要界面推荐 Qdrant、Milvus、Chroma。已经上了云可以用云厂商的向量检索服务。选择向量库时不要只看性能要关注三点是否支持过滤条件比如按时间、类型过滤。是否方便备份和迁移。是否有成熟的 Python/Java 客户端。6.5 RAG 效果差的常见原因RAG 效果不好通常不是模型问题而是这几个环节出了问题切分不合理块太大检索粒度粗块太小语义不完整。缺失标题信息切块丢掉了所在章节的标题上下文不完整。Embedding 和检索算法不匹配比如数据用 A 模型向量化线上却用 B 模型向量化。直接暴露原始片段没有对检索结果做清洗或重排。7. 核心工具五LLM Wiki 知识管理工具7.1 什么是 LLM Wiki这两年和“个人知识管理”相关的概念里比较流行的是“LLM Wiki”范式。它的核心不是写笔记而是把个人知识库变成“模型可以协作维护的 Wiki”。简单理解就是你维护一堆 Markdown 文件存储项目经验、踩坑记录。LLM 定期扫描这些文件生成摘要、双链、索引和知识图谱。你提问时LLM 基于这堆文件回答并主动建议“这个问题在某某文档里记录过”。这套思路对个人开发者特别实用。很多经验和代码片段散落在不同项目里与其事后写长篇总结不如让 LLM 每天帮你整理成 Wiki。7.2 一个最小实现思路假设你的笔记目录是~/notes里面都是 Markdown 文件。最小实现可以这样设计# 文件路径llm-toolkit/wiki/wiki_builder.py import os import re def scan_notes(note_dir: str): 扫描笔记目录返回所有 Markdown 文件路径。 files [] for root, _, filenames in os.walk(note_dir): for name in filenames: if name.endswith(.md): files.append(os.path.join(root, name)) return files def extract_titles(content: str): 从 Markdown 里提取标题作为 Wiki 的节点。 titles [] for line in content.splitlines(): line line.strip() if line.startswith(#) and not line.startswith(# ): level len(line) - len(line.lstrip(#)) title_text line.lstrip(#).strip() titles.append({level: level, title: title_text}) return titles def build_wiki_index(note_dir: str): 构建简单的 Wiki 索引。 index [] for path in scan_notes(note_dir): with open(path, r, encodingutf-8) as f: content f.read() titles extract_titles(content) index.append( { file: path, title: os.path.basename(path).replace(.md, ), headings: titles, # 可以把摘要生成交给 LLM summary: , } ) return index if __name__ __main__: index build_wiki_index(~/notes) for item in index: print(item[file], |, item[title])如果你喜欢用 Obsidian也可以再做一个插件把生成的索引和知识图谱直接落到笔记库里形成“双向链接”。这里不展开插件开发重点是这个思路让 LLM 做知识库的整理者和问答助手而不是让知识库堆在文件夹里积灰。7.3 不要把 Wiki 做成文档垃圾场LLM Wiki 最大的坑是“只堆不整理”。文档越多索引越乱最后模型也无法给出高质量回答。我的建议是每个文档保持单一主题。开头写清楚背景和结论。保留代码示例和命令。定期让 LLM 做一次去重和合并。8. 精度管理FP16、FP32、BF16 怎么选本地部署模型时精度选择是绕不开的问题也是报错重灾区。8.1 三种精度原理先说结论FP32单精度浮点数占 4 字节。范围大精度高但显存占用也最大。FP16半精度浮点数占 2 字节。显存占用减半计算更快但动态范围有限容易出现上溢或下溢。BF16BFloat16也是 2 字节但是指数位和 FP32 一样多动态范围大适合训练和推理。缺点是尾数位少精度相对低。训练时 BF16 用得比较多原因是模型梯度变化范围大BF16 不容易溢出。推理时 FP16 比 BF16 更常用因为推理场景更看重精度保留能力。8.2 显存占用公式一个直观的估算公式模型显存 ≈ 参数量(A) × 字节数(B)结合常见精度精度每参数占用7B 模型大约占用FP324 字节28GBFP162 字节14GBBF162 字节14GBINT8 量化1 字节7GBINT4 量化0.5 字节3.5GB注意这只是模型权重本身。推理时还需要额外空间存 attention、KV Cache 等临时状态实际占用更高。8.3 实践选择建议显存充足24GB 以上直接用 FP16省心。显存吃紧8GB 到 12GB考虑量化模型或选 7B 以下的模型。追求推理性能优先 INT8 或 INT4 量化但要评估回答质量损失。训练/微调场景优先 BF16。实际开发中不要只看“能跑起来”要对比同一批测试用例在不同精度下的输出质量再决定用哪个精度。9. 常见问题与排查清单9.1 高频问题表问题现象常见原因解决思路本地模型加载慢模型文件过大或磁盘读取慢换量化模型使用 SSD显存不足 OOM模型参数量超过显卡能力减小模型、降低精度、开启量化调用模型接口超时网络问题或模型推理时间过长调大 timeout启用流式输出上下文窗口截断文档/历史太长超限做文本压缩、历史裁剪、分段处理向量检索结果不相关切块不合理或 Embedding 不匹配调整 chunk size统一向量模型工具调用不生效工具描述格式和 API 要求不一致对照官方 schema 检查参数结构模型输出乱跳、格式不稳定温度太高或 Prompt 指令不明确降低 temperature增加输出格式约束9.2 排查顺序建议遇到 LLM 工具链问题时不要一上来就怀疑模型按这个顺序排查先确认请求有没有到服务端。确认模型名、参数格式是否合法。看服务端日志是否有异常。用最小请求单独测试排除应用层干扰。最后才考虑换模型或调整 Prompt。10. 最佳实践与工程建议10.1 统一入口隔离模型不管用几个模型业务层都只面向一个网关入口。这样后续切换模型、做灰度、做降级都不需要修改上层代码。10.2 密钥管理永远不要把 API Key 写在代码或仓库里。使用环境变量或配置中心管理并设置最小权限能用只读就不给读写能用一个项目就不给全局权限。10.3 流式还是非流式Web 场景优先使用流式输出这样用户不用等待整个回答生成体验更接近“打字机”效果。网关层要对流式响应做透传支持不要让长任务卡住连接。10.4 日志与成本统计每次调用都应该记录模型名称与版本。Prompt 和输出长度。Token 消耗。响应耗时。错误码。这些日志是排查线上问题和优化成本的基础。10.5 Agent 工具权限最小化给模型注册工具时遵循最小权限原则。模型只需要读就不要给写权限只需要查单条数据就不要开放全表查询。工具越多被误调用的概率越高一定要控制暴露面。10.6 小步验证持续对比从本地小模型验证流程再到云端大模型跑正式结果。每次更换模型或精度都保留一批固定测试用例做回归对比。LLM 是概率系统没有连续性测试很难发现“某次调整后变差了”。11. 总结这套 LLM 工具链的核心思路并不是某一个框架或者某一家平台而是把模型接入、模型路由、工具调用、知识检索、个人 Wiki 这几个环节理顺让模型在可控的边界内发挥能力。日常开发中我自己的优先级排序是先保证本地推理流程可复现。再通过网关统一模型入口。接着把 Agent 工具注册表和 RAG 检索流程稳定下来。最后用 LLM Wiki 把经验沉淀住。如果你正打算从“调 API 示例”走向“完整 LLM 应用”可以把本文的工程结构当成起点先跑通最小闭环再根据业务需要逐步扩展工具。每一步都建议在真实场景里跑一段时间再决定要不要加功能。工具链从来不是越复杂越好而是刚好够自己用、又能持续演进才是最好的状态。
返回列表