看到这个标题我就明白了这是一位被传统环境“困住”的研发者在求解。LAMP这四个字母在过去十几年几乎就是Web服务的代名词——Linux、Apache、MySQL、PHP。但今天要把RAG、Agent、LLM塞进同一个环境里部署问题就变得很有嚼头了一台跑了好几年常规业务的服务器能不能同时扛住大模型推理MySQL那套表结构还能不能继续用来做向量检索PHP页面怎么跟Python写的Agent服务通信这篇文章就是围绕“LAMP环境部署RAG Agent LLM”这个场景把我实际踩过的坑、最终跑通的方案、以及每一步为什么要这么做的逻辑一次性讲清楚。这套东西适合谁看首先是手里已经有LAMP/LNMP环境、不想推倒重来的后端工程师或运维。其次是准备在企业内网做私有化AI应用、但硬件条件有限的团队。最后是那些被“GPU服务器 重型框架”方案劝退、想用最小成本验证RAG和Agent流程的人。我会尽量按我实际落地的顺序来写从架构思路到环境评估从核心服务部署到问题排查全程保持“可复现”的标准。1. 从LAMP到AI应用架构演进思路拆解1.1 LAMP环境在AI时代的角色定位很多人的第一反应是LAMP是给PHP网站用的跟AI八竿子打不着。但实际部署下来你会发现LAMP真正值钱的部分是Linux和MySQLApache和PHP只是历史包袱未必需要全盘保留。我在这次部署里做了一个关键决定保留Linux作为操作系统底座保留MySQL作为业务数据与元数据存储但把Apache的流量入口角色逐步让给反向代理层PHP则完全不动只负责老业务。新增的AI服务全部用Python实现通过独立进程跑在服务器上和原有Web服务通过HTTP接口对接。为什么要这么设计原因有三个。第一LLM推理服务本身是长连接、高内存占用的进程跟PHP-FPM这种短生命周期进程混在一起容易互相拖垮必须做进程隔离。第二RAG需要的向量检索能力MySQL虽然有空间数据支持但真正适配向量场景还是得靠专门的索引或插件我在后面会详细讲。第三Agent调度层需要异步、流式、WebSocket这类通信能力Apache的mod_php模式给不了用Python异步框架是更合理的选择。所以这里有个核心认知要扭转LAMP不是要被替换掉而是要以它为基础长出一个新的AI服务层。传统服务继续服务传统请求AI服务独立运行、按需扩展两者通过明确定义的API边界协作这套架构既能利用现有服务器资源又不会让老业务冒太大风险。1.2 RAG、Agent、LLM三者的协作关系在部署之前我必须把这三者的关系理顺因为很多人部署失败的根本原因不是技术不行而是没搞清楚它们各自该干什么。打个比方LLM像一个刚入职、知识面很广但不了解你们公司具体业务的新人。他知道怎么写作、怎么推理但你问他“咱们内部报销流程怎么走”他大概率会胡说八道。RAG就是给他配了一个内部资料库的查询权限他能先从资料库里找到相关内容再结合自己的语言能力组织回答。Agent则是那个协调工作的主管他会拆解任务、决定什么时候查资料、什么时候调用工具、什么时候直接回答。所以在系统架构上LLM是推理引擎可以本地部署也可以调用远端APIRAG是外挂知识库由文档解析、向量化、检索三个环节组成Agent是业务调度层负责理解用户意图、调用RAG检索、组织Prompt、调用LLM生成最终答案。我在实际部署中把这三层分成了三个独立服务分别跑在11434端口LLM服务、8000端口Agent服务加上MySQL的3306端口这样每个服务都能单独重启、单独排查日志互不干扰。2. 部署前的环境评估与方案选型2.1 硬件与系统层面的基础检查这三件套最耗资源的是LLM推理。在确定方案之前我先在目标服务器上跑了一轮基础检查重点关注三个指标内存、CPU、磁盘。内存是第一道关卡。以我这次部署为例选了一个7B参数的中文对话模型FP16精度加载大概需要约14GB显存或内存。如果没有GPU纯CPU推理会慢很多需要预留至少16GB内存给模型本身加上操作系统、MySQL、Agent服务各占2GB左右所以我的底线是物理内存不低于32GB。这个数值不是什么玄学7B参数乘以2字节FP16得到14GB再算上KV Cache和推理框架开销必须留足余量。CPU方面纯CPU推理时推理速度主要取决于内存带宽而不是核心数。实测下来一台8核16GB的服务器跑7B模型每秒大约只能生成2到3个token聊天勉强能用但全文总结类任务体验很差。所以我建议如果预算允许优先搞一块消费级显卡24GB显存那种如果没有GPU就把模型降到3B到4B级别或者把并发用户数限制在1到2个。磁盘检查容易被忽略其实非常关键。模型文件动辄10GB以上向量库的数据增长也很快。我给AI服务单独分了一个数据盘挂载到/data目录所有模型权重、向量数据库文件、日志全部放在这个盘里。这样即使系统盘损坏AI服务的数据也不会丢而且后续扩展存储也方便。系统层面还有几个必须做的检查项内核版本要支持OverlayFSDocker用或至少是3.10以上方便后续容器化防火墙规则要确认3306、8000、11434这几个端口不会被外部随意访问swap分区建议开8GB虽然模型推理时不希望跑到swap但真遇到内存峰值它能救命。2.2 软件栈选型保留什么、替换什么、新增什么很多教程上来就让你装Docker、装Kubernetes但对LAMP环境来说这不是最优解。我的方案是“最小改造 渐进融合”决策逻辑如下表所示。原有组件这次部署的角色处理方式原因Linux基础操作系统原样保留稳定所有AI组件都支持ApacheWeb服务入口保留但叠加反向代理配置老业务URL不能断MySQL业务数据 元数据原样保留增加向量表轻量RAG场景够用PHP老业务逻辑完全不动不建议强行走AI逻辑PythonAI服务主语言新增Python3.10RAG/Agent生态最成熟向量检索RAG的记忆存储新增MySQL向量插件或专用表避免引入额外数据库依赖软件选型上我最想强调的是不要盲目上重型框架。我见过一个团队为了RAG直接部署了完整的分布式向量数据库集群结果数据量才几千条文档纯属杀鸡用牛刀。在LAMP环境里文档量在十万级以下、单条文档长度不超过几千字时用MySQL加一个向量字段配合相似度检索函数完全够用。只有数据量突破百万级、需要毫秒级响应的时候再考虑引入独立的向量数据库。LLM推理框架的选择同样如此。vLLM吞吐量高但对显存和CUDA版本有要求Ollama安装简单CPU也能跑llama.cpp则适合极致抠内存的场景。我这次的选型是有GPU时用Ollama操作简单、接口兼容OpenAI格式、后续迁移方便纯CPU环境用llama.cpp量化后能用更少内存跑同样模型。这两个框架都支持HTTP API对Agent服务的集成非常友好。Agent层的选型也遵循同样的逻辑。LangChain功能全但引用关系复杂、版本升级频繁部署在资源受限的老服务器上经常会因为依赖冲突报错。我最终选择了自己写一个轻量调度核心用FastAPI做服务框架把“调用LLM”“调用RAG检索”“组装Prompt”三个动作做成可配置的流程总代码量不大而且完全可控。这个取舍在后面的实操环节会展开说。3. RAG Agent LLM部署实操记录3.1 LLM推理服务的落地配置LLM推理是整个系统的地基地基不稳后面RAG和Agent全部白搭。在LAMP环境里我建议第一步先把LLM服务跑起来并做验证再去做RAG检索。还是在Ollama方案的框架下安装过程极其简单一条命令就能完成curl -fsSL https://ollama.com/install.sh | sh但我建议不要直接裸装完就用因为默认配置只监听本机回环地址Agent服务如果跑在其他机器上就连不上。我修改了systemd服务加入环境变量让它监听内网地址sudo systemctl edit ollama[Service] EnvironmentOLLAMA_HOST0.0.0.0:11434 EnvironmentOLLAMA_MODELS/data/ollama/models EnvironmentOLLAMA_MAX_LOADED_MODELS1 EnvironmentOLLAMA_NUM_PARALLEL2OLLAMA_MODELS指定模型存放目录放在大容量数据盘OLLAMA_MAX_LOADED_MODELS1强制只保留一个模型在内存里避免多个模型同时加载把内存撑爆OLLAMA_NUM_PARALLEL2允许两个并发请求共享模型上下文这个参数别调太大并发越高单用户响应越慢。然后拉取模型并测试ollama pull qwen2.5:7b-instruct ollama run qwen2.5:7b-instruct 你好请用一句话介绍你自己这一步验证通过后我用curl确认API可访问curl http://127.0.0.1:11434/api/generate -d { model: qwen2.5:7b-instruct, prompt: 11?, stream: false }返回的JSON里能看到response:2说明LLM服务已经就绪。需要提醒的是首次加载模型会有一段较长的等待时间表面看起来像卡死了实际上是在把模型权重读进内存几秒钟到几十秒都正常。如果这个阶段就报OOM说明模型参数超过内存容量需要换更小的量化模型。3.2 RAG检索模块的构建思路RAG模块的核心工作就三件事切文档、算向量、存检索。我在这次部署里用MySQL做向量存储可通过以下步骤落地。如果用的是MySQL 8.0以上版本且装了向量插件可以建带向量字段的表CREATE DATABASE IF NOT EXISTS rag_kb CHARACTER SET utf8mb4; USE rag_kb; CREATE TABLE doc_segments ( id INT AUTO_INCREMENT PRIMARY KEY, doc_name VARCHAR(255) NOT NULL, chunk_index INT NOT NULL, content TEXT NOT NULL, embedding VECTOR(1024), -- 假设用的是1024维的向量模型 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_doc_name (doc_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;如果你的MySQL没有向量支持可以退而求其次用二进制字段存储向量再用应用层做相似度计算。不过数据量小几千条文档时这个方案也能跑数据量大了再迁移独立的向量数据库。文档切块这个环节最容易翻车。切得太碎语义被割裂检索不精准切得太大超过模型上下文窗口限制反而浪费token。我采用的是按标题 段落 固定长度三重策略先按Markdown标题把文档切成“一级章节”每个章节至少500字。如果一个章节超过800字按句子边界二次切分保证每个块大约600字。在保存前给每个块额外保存两层相邻块的ID方便召回时做上下文扩展。这个策略保证了RAG检索到的内容既有足够上下文又不至于超出LLM单次处理窗口。实际使用时我在Agent服务里做了“召回Top3块 每块左右邻居拼接”的逻辑效果比单纯Top3好很多用户明显感知到回答更连贯了。向量化模型方面我选用了一个针对中文优化的Embedding模型维度1024建议用以下代码做批量向量化import numpy as np from sentence_transformers import SentenceTransformer # 建议把模型权重放数据盘首次下载会比较慢 model SentenceTransformer(/data/models/bge-large-zh) def embed_texts(texts): vectors model.encode(texts, normalize_embeddingsTrue) return [vec.astype(np.float32) for vec in vectors] # 批量插入MySQL一次500条避免长事务 def batch_insert(records): values [] params [] for rec in records: values.append((%s, %s, %s, %s)) params.extend([rec[doc_name], rec[chunk_index], rec[content], rec[embedding].tobytes()]) sql fINSERT INTO doc_segments (doc_name, chunk_index, content, embedding) VALUES {, .join(values)} cursor.execute(sql, params) conn.commit()特别注意普通用户最容易踩的坑是忘了做向量归一化。后续检索用余弦相似度计算如果插入时不归一化检索出来的相近度得分会受文本长度影响导致长文档莫名其妙被排到前面。在Embedding模型工具里加normalize_embeddingsTrue这个参数可以解决。检索时MySQL里可以直接执行相似度查询无论用插件函数还是应用层计算都要注意效率。我在实现中先通过SQL的WHERE限定文档范围再在应用层做TopK排序有效避免全库扫描。3.3 Agent调度层的集成方式有了LLM和RAG两块地基Agent层的作用就是把它们接到业务请求上。我在这里选择用FastAPI写一个轻量Agent服务很简单、直观也够用。from fastapi import FastAPI, Request from pydantic import BaseModel import requests import numpy as np app FastAPI() # RAG检索 def retrieve(query: str, top_k: int 3): q_vec embed_texts([query])[0] cursor.execute( SELECT id, doc_name, content, embedding FROM doc_segments ORDER BY VECTOR_COSINE_DISTANCE(embedding, %s) LIMIT %s , (q_vec.tobytes(), top_k)) return [row[2] for row in cursor.fetchall()] # LLM调用 def call_llm(system_msg, user_msg): resp requests.post( http://127.0.0.1:11434/api/chat, json{model: qwen2.5:7b-instruct, messages: [ {role: system, content: system_msg}, {role: user, content: user_msg} ]}, streamFalse ) return resp.json()[message][content] class ChatRequest(BaseModel): question: str app.post(/api/chat) async def chat(req: ChatRequest): messages [{role: system, content: 你是一个企业内部知识助手回答时优先依据提供资料。}] retrieval_results retrieve(req.question, 3) context \n\n.join(retrieval_results) user_msg f资料\n{context}\n\n问题{req.question}\n请基于资料作答。 answer call_llm(, user_msg) return {answer: answer, sources: retrieval_results[:3]}这个服务的核心在于Prompt组装逻辑。RAG检索到的资料不是直接塞给LLM就行而是必须在Prompt里明确告诉模型“哪些是资料内容、哪些是用户提问、回答时什么情况下使用资料、什么情况下告知资料不足”。我试过不加这些约束模型在资料不足时还是会胡编加了约束之后正确率有明显提升。Agent服务启动方式也很简单cd /data/agent nohup uvicorn main:app --host 0.0.0.0 --port 8000 agent.log 21 启动后测试接口curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {question: 我们的报销流程包含哪几个环节}返回结果里既有回答文本又有来源文档说明RAG和LLM已经打通了。至此一个最小的“LAMP环境 RAG Agent LLM”闭环已经成立。不过我这个Agent示例是最简化的版本还缺少记忆、多轮对话、工具调用。如果你需要的是完整Agent能力建议在这些基础上增加会话历史存储可以放Redis或MySQL、工具注册机制让模型能调用检索、查询数据库等操作、任务规划循环模型可以拆解子任务并逐一执行。这些扩展方向我会在最后再聊。4. 常见问题与排查技巧实录4.1 内存不足导致的OOM问题这是LAMP服务器部署LLM时最高频的问题。因为很多老服务器是16GB内存甚至8GB却在ollama pull时直接选了7B模型结果一加载系统就卡死SSH操作都迟钝。我排查这类问题的固定流程是先跑free -g看内存分配再跑ollama ps看当前加载的模型显存占用最后看系统日志journalctl -u ollama确认退出原因。绝大多数情况就一个解决办法——换更小的模型或者更狠的量化版本。比如7B模型用Q4_K_M量化后只需要大约4GB内存比FP16节省了七成。另外一个我实践出来的建议是给Ollama设置模型并发数为1然后在Agent服务层做排队不要指望LLM服务自身扛并发。我之前图省事把OLLAMA_NUM_PARALLEL调到4结果4个用户同时提问每个用户等了一个多小时才出结果体验极差后来改回2。4.2 向量检索不准或结果为空向量检索出问题先别急着调相似度阈值先检查是不是当时没把几个基础环节做对。第一检查Embedding模型和检索时用的是否同一个模型。我见过有人建库时用中文模型查询时换成了通用模型向量维度都不一样结果肯定对不上。第二检查向量是否做了归一化。若不归一化会导致余弦相似度指标失真长文档被错误排在前面这个前面已经强调过是最隐蔽的坑。第三检查检索前有没有做查询改写。用户的自然语言提问和文档里的表述往往不同。比如用户问“离职要办什么手续”文档里写的是“员工退出流程包含交接、社保转出、离职证明开具”。这两个句子字面差异很大直接检索可能召回不到最相关的部分。在Agent体系里做查询改写很容易让LLM先把问题改写成“关键词组合”的形式再拿改写结果去检索。实测这个操作能明显提升中文RAG的检索命中率。我的一次实测数据是用原始问题检索Top3命中率约为62%做改写后升到81%值得一试。4.3 传统Web服务与AI服务的端口和进程管理冲突LAMP环境里老服务先占用了80或443端口AI服务则跑在8000或11434本身不冲突但如果配置了反向代理就容易出问题。我遇到过一次Apache配置里写了ProxyPass /ai http://127.0.0.1:8000但因为Apache没有启动mod_proxy模块结果所有到/ai的请求都返回500。排查方法很简单sudo a2enmod proxy proxy_http sudo systemctl reload apache2另外还要注意反向代理的超时设置。LLM生成一段长回答可能耗时几十秒而Apache默认的ProxyTimeout可能只有30秒超出就断连了。我把它调到了300秒ProxyTimeout 300还有一个老生常谈的安全问题AI服务端口尽量不要直接暴露到公网。我在部署时用iptables只允许内网访问8000和11434只有80/443端口开放给外部再通过Apache反向代理对外提供服务。这样即使AI服务有漏洞攻击者也无法直接攻击AI端口。4.4 模型推理速度慢如何优化CPU推理慢是常态除非换硬件否则优化空间有限。但我还是整理了几个在现有条件下能榨出性能的手段。一个是使用量化模型。同样的7B模型FP16用14GB内存Q4量化后只需4GB内存推理速度也快得多。代价是回答质量有一定下降但日常的RAG问答场景区别不大。实际部署中我优先用Q4_K_M量化版本。另一个是控制上下文长度。大模型的推理耗时随token数急剧上升而RAG场景最容易造成上下文膨胀——把一堆资料、历史记录全塞给模型。我的做法是Prompt总长度控制在模型上下文窗口的四分之一以内多出的历史记录截断保证首字响应在几秒内。最后还有一个容易被忽略的点注意CPU频率管理策略。服务器默认可能在省电模式CPU频率上不去推理自然慢。把CPU调成性能模式后实测推理速度有20%左右的提升sudo cpupower frequency-set -g performance5. 扩展空间与个人实践体会这套基础架构跑通之后后面可做的事还挺多。如果企业内部有多个部门的知识库可以在RAG层按部门或文档类型拆分向量集合给Agent加一层权限路由逻辑不同角色只能检索自己有权限的文档。如果用户量上来可以把LLM推理服务从单机Ollama替换成支持分布式推理的vLLM集群Agent层代码基本不用改因为API兼容。如果需要对Agent能力做扩展可以加入工具调用机制让Agent具备查数据库、发通知、生成报表这类动作能力而不仅仅是回答问题。我在整个部署过程中最大的体会是把AI服务塞进传统LAMP环境难点从来不在AI技术本身而在传统运维与AI工程这两种思维模式的碰撞。传统Web服务的逻辑是“短请求、快响应、高并发”AI推理的逻辑是“长耗时、大内存、流式输出”。谁能更快地转换思路谁就能少吃苦头。如果你现在正对着服务器发愁不妨按照我上面的步骤先去把LLM服务跑通再一步步加上RAG和Agent。别想着一步到位搭一个完美的AI平台先把最小闭环跑起来后续的优化和改进都是在真实使用中不断沉淀出来的。这套路数我在多个项目上验证过稳。