ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大模型LLM工程实战:从接口接入到RAG问答的完整指南

大模型LLM工程实战:从接口接入到RAG问答的完整指南 简介这是一份面向大语言模型与AIGC工程实践者的实例代码合集围绕2023年主流LLM应用开发展开覆盖ChatGLM模型使用、LangChain框架、ChatPDF知识库问答、向量数据库以及Stable Diffusion和Midjourney生成式AI五大专题适合希望从模型调用走向端到端应用搭建的开发者学习参考。资源内含101个文件以36个ipynb代码笔记和14个py脚本为核心辅以可运行的样例数据、配置文件、模型索引及说明文档按专题分目录组织整体压缩包138.21MB便于按需查阅和直接运行调试。目前已有340人学习/下载内容既有完整实现脚本也有关键流程的notebook拆解可帮助读者快速理解ChatGLM对话、LangChain工具编排、ChatPDF检索、向量数据库存储以及AI绘画等场景的工程实现。无论是入门大模型应用开发还是寻找现成代码做二次改造这份合集都是一份实用的参考。1. 2023年大模型LLM工程实例代码合集先看清这份压缩包真正该教你的东西拿到一份命名为“2023大型语言模型-aigc-LLM-engineering实例代码合集.zip”的资源多数人第一反应是解压、找 README、跑通一个 demo。但真正有工程经验的开发者会先做另一件事按“模型接入层、提示词工程层、应用逻辑层、评估层”四个维度把里头的文件归类。因为 2023 年的LLM工程代码有一个鲜明特征——模型能力迭代快、框架未定型示例代码的价值不在“能跑”而在“暴露了哪些工程约束”。这组代码通常在讲一件事如何把 GPT 类大模型、开源LLM、向量检索和业务逻辑串成一条可维护的生产链路。适合两类读者刚接触 RAG 和 prompt engineering 的后端工程师以及想从“调 API”升级到“做 AI 应用架构”的技术负责人。本文不逐行解读某个具体压缩包而是按这类合集最常见的模块划分把选型理由、参数设置和排错路径一次讲透。2. LLM工程接入层从统一客户端到本地推理的环境准备2.1 为什么示例代码合集偏爱“OpenAI 兼容接口”而不是各家裸 SDKLLM工程里最容易被低估的是接口兼容层。2023 年开源的 LLM 推理服务如 vLLM、TGI、llama.cpp 的 server 模式几乎都实现了 OpenAI 风格的/v1/chat/completions接口而闭源模型厂商也纷纷提供兼容端点。这意味着你的业务代码只要写一套客户端就能在“云端商用模型”和“内网开源模型”之间切换。示例代码合集里最常见的结构就是llm_client.py加.env配置这也是我推荐的最小架构。2.1.1 用环境变量隔离模型端点我一般会让所有环境配置走.env文件而不是硬编码在代码里。理由是同一套代码要在开发、测试、生产三个环境跑模型供应商和 key 都不一样。# config.py import os from dotenv import load_dotenv load_dotenv() LLM_BASE_URL os.getenv(LLM_BASE_URL, https://api.openai.com/v1) LLM_API_KEY os.getenv(LLM_API_KEY, ) LLM_MODEL os.getenv(LLM_MODEL, gpt-3.5-turbo) EMBEDDING_MODEL os.getenv(EMBEDDING_MODEL, text-embedding-3-small)这里把base_url、api_key、model三个参数拆开核心考虑是迁移成本。当你想把请求切到本地 llama.cpp 服务时只需把LLM_BASE_URL改成http://127.0.0.1:8080/v1代码完全不动。这也是 2023 年大量 aigc 项目能快速从 demo 转生产的根本原因——接口兼容性比模型本身的 benchmark 分数更能决定落地速度。2.2 用 requests 还是 openai SDK按可控性选很多示例代码直接用openaiPython SDK因为它支持流式、重试和超时设置。但如果你需要细粒度控制连接池和日志我建议封装一层 requests 调用。下面是一个最小实现# llm_client.py import json import requests from config import LLM_BASE_URL, LLM_API_KEY, LLM_MODEL def chat_completion(messages, temperature0.7, max_tokens512, streamFalse): url f{LLM_BASE_URL}/chat/completions headers { Content-Type: application/json, Authorization: fBearer {LLM_API_KEY}, } payload { model: LLM_MODEL, messages: messages, temperature: temperature, max_tokens: max_tokens, stream: stream, } resp requests.post(url, headersheaders, jsonpayload, timeout60) resp.raise_for_status() data resp.json() return data[choices][0][message][content], data[usage]temperature0.7是平衡创造性和稳定性的常见起点偏向文案生成可以调高到 0.9偏向信息抽取则调到 0.2 以下。max_tokens512限定生成长度防止成本失控。返回的usage对象包含prompt_tokens、completion_tokens、total_tokens这是后续做成本核算的原始数据务必保留。2.3 本地推理方案的环境坑Python 版本和模型格式如果集合里的代码涉及本地加载开源模型大概率会碰到两个环境问题。第一Python 版本要求2023 年主流的 LLM 推理库对 3.8 以下支持很差建议直接上 3.10 或 3.11 并新建虚拟环境。第二模型权重格式Hugging Face 上常见pytorch_model.bin和safetensors两种格式如果代码里用from_pretrained加载报错优先检查safetensors是否安装、显存是否满足load_in_8bit的条件。推理方案显存需求7B 模型适合场景主要限制llama.cppGGUF 量化4GB-8GB个人电脑、CPU 推理上下文窗口受限、并发低vLLM16GB 以上高并发生产环境需要 CUDA 环境、显存紧张Hugging Face Transformers14GBfp16开发调试、微调改造推理速度慢、不适合线上这里提醒一点不要盲目追求“本地跑 7B 模型”。如果示例合集里同时给出了 API 调用和本地推理两套代码优先跑通前者。理由是本地推理的依赖矩阵复杂pydantic、tokenizers、CUDA 版本任何一个不对都能消耗半天时间。先把 API 链路跑通再回头攻本地推理心理压力完全不同。3. prompt engineering 可复现的三种写法模板、结构化输出和上下文管理3.1 用 Jinja2 管理 prompt 模板而不是 f-stringLLM 工程中 prompt 是易变资产。直接写在业务代码里的 f-string 一旦要改语气或加 few-shot 示例就得发版。更稳妥的做法是用 Jinja2 模板文件管理 prompt让运营和算法同学可以独立调参。{% raw %} !-- prompts/classification.jinja2 -- 你是一个意图分类引擎。根据用户输入从以下类别中选择一个并输出 JSON [退款, 改签, 咨询, 投诉] examples 用户我要退这张票 类别退款 用户能换成明天下午的吗 类别改签 /examples 用户{{ user_input }} 类别 {% endraw %}在业务代码里加载这个模板并填充变量# prompt_render.py from jinja2 import Environment, FileSystemLoader import json from llm_client import chat_completion env Environment(loaderFileSystemLoader(prompts)) def classify_intent(user_input: str) - dict: template env.get_template(classification.jinja2) prompt template.render(user_inputuser_input) content, _ chat_completion( messages[{role: user, content: prompt}], temperature0.1, max_tokens50, ) return json.loads(content)这样做的好处是prompt 内容不再散落在 Python 字符串里模板的版本可以进 Git 单独管理。temperature0.1保证分类任务输出稳定不会出现同一个输入两次结果不同的尴尬。Jinja2 模板中的{% raw %}块只是展示层写法实际使用时直接写纯文本和变量占位。如果模型返回的 JSON 解析失败建议在json.loads外层包一个 try-except并记录原始输出供排查。3.2 打破 JSON 解析魔咒函数调用与正则兜底2023 年的 LLM 工程代码里最常出现的运行时报错就是JSONDecodeError。原因很直接——模型输出“看起来像 JSON 但不完全是”比如首尾多了解释性文字或使用了单引号。示例合集里通常会给出三种策略第一种是要求模型只输出 JSON靠指令约束第二种是利用函数调用function calling让模型走结构化参数返回第三种是正则抽取后json.loads。import re import json def safe_json_parse(text: str) - dict: # 先尝试直接解析 try: return json.loads(text) except json.JSONDecodeError: pass # 再尝试抽取第一个 { 到最后一个 } 之间的内容 pattern r\{.*\} match re.search(pattern, text, re.DOTALL) if match: try: return json.loads(match.group()) except json.JSONDecodeError: pass # 最后尝试把单引号替换为双引号 fixed re.sub(r(?!\w)([^]*)(?!\w), r\1, text) return json.loads(fixed)这个兜底函数的逻辑分三层递进。第一层直接解析成本最低第二层应对模型输出前后有杂音的情况第三层处理单双引号混用的常见畸形输出。真实工程里第三层能救回 10% 左右的失败请求但代价是可能误伤字符串内的单引号内容如its。所以落到生产环境我更推荐直接用函数调用参数它的输出天然是合法 JSON且一次请求能同时完成格式化和业务解析。3.3 上下文管理的三个参数max_tokens、system message 和 token 预算LLM 实例代码里最常见的 bug 是“多轮对话越聊越笨”根因是没控制历史消息长度。对话一长messages数组超出模型上下文窗口新增消息被截断模型丢失早期信息。我习惯在每次请求前做一次 token 预算计算参数推荐值作用max_tokens2048限制单次生成长度预留响应空间历史消息压缩前长度不超过上下文 50%留出检索结果和 prompt 的位置system message 长度不超过 500 token避免指令被淹没温度0.2-0.7按任务稳定性要求调整一个朴素但有效的做法是始终把system消息放在数组首位业务无关的历史消息做滑动窗口截断只保留最近 N 轮。N 的取值取决于上下文窗口以 8K 窗口为例每轮约 300 token保留 10 轮以内是安全的。代码中我会用tiktoken计算 token 数并做截断而不是靠字符数估算——中文场景下字符数和 token 数的比例约为 1:1.5不精确的计算会导致窗口溢出。4. 实战核心模块基于 AIGC 与 LLM 的 RAG 文档问答落地4.1 文档问答的最小链路切分、嵌入、检索、生成RAG检索增强生成几乎成了 2023 年 LLM 工程实例代码合集中最厚的一个模块。它的核心思路不是让模型“记住”你的私有文档而是在每次请求时先从知识库检索相关内容拼进 prompt 再让模型回答。关键链路四步文档切分、向量嵌入、相似度检索、生成回答。# rag_pipeline.py from config import LLM_BASE_URL, LLM_API_KEY import requests # 1. 文档切分按固定 chunk_size 做重叠切分 def split_text(text, chunk_size500, overlap50): chunks [] start 0 while start chunk_size len(text): chunks.append(text[start:start chunk_size]) start chunk_size - overlap return chunks # 2. 嵌入调用 embedding 接口转成向量 def get_embedding(text): url f{LLM_BASE_URL}/embeddings resp requests.post(url, headers{ Authorization: fBearer {LLM_API_KEY}, Content-Type: application/json }, json{model: text-embedding-3-small, input: text}) return resp.json()[data][0][embedding] # 3. 检索向量点积相似度计算 def search(query_embedding, doc_embeddings, top_k3): scores [] for i, doc_emb in enumerate(doc_embeddings): score sum(a * b for a, b in zip(query_embedding, doc_emb)) scores.append((i, score)) scores.sort(keylambda x: x[1], reverseTrue) return scores[:top_k]chunk_size500与overlap50的组合在中文技术文档上表现比较稳定。500 字符既不会让语义断裂又不至于超出 embedding 模型的输入上限。重叠 50 字符是保证两个切片边界上的关键信息不丢。向量点积相似度在 embedding 模型已做归一化时等价于余弦相似度这是示例代码里最常见的写法。如果你的代码里没有做归一化建议显式计算余弦相似度cosine dot(a, b) / (norm(a) * norm(b))。4.2 检索质量的两个关键调优点top_k 与重排top_k 参数直接决定“模型能看到多少资料”。设小了答案可能缺失关键依据设大了上下文窗口被无关内容占满。我在生产环境里的经验是基础检索top_k5放入重排后再取前 3 条作为最终上下文。两步策略的目的是控制 prompt 长度并保证信息密度。# rerank.py def rerank(query, candidates, modelgpt-3.5-turbo): prompt f根据问题与文档的相关程度输出相关度最高的文档序号从1开始只输出逗号分隔的序号\n\n问题{query}\n\n for i, doc in enumerate(candidates): prompt f[{i 1}] {doc[:200]}\n content, _ chat_completion( messages[{role: user, content: prompt}], temperature0.0, max_tokens32, ) return [int(x) - 1 for x in content.split(,) if x.strip()][:3]这个重排方法是“用大模型做小事情”的典型写法效果比纯向量相似度高不少但会多一次 LLM 调用。如果不希望增加延迟也可以用cross-encoder模型做重排只是需要多维护一个模型文件。关键约束是max_tokens32重排只需要输出序号列表不需要生成理由长输出只会浪费 token 和时间。4.3 文档问答失败的三个信号和对应排查路径现象可能原因排查动作回答与文档矛盾检索到的片段不相关模型凭内部知识作答检查 top_k 召回内容的相似度分数低于阈值的片段直接丢弃答非所问或内容重复上下文被无关 chunk 淹没调小 chunk_size增加重排步骤输出与文档一致但缺少引用来源没有把文档标题或来源拼进上下文切分时保留source字段并在 prompt 中要求引用我在排错时一定会看日志里的“检索片段原文”而不是只看最终回答。原因很简单RAG 的生成质量上限由检索质量决定模型只是“复述”检索到的内容。如果检索返回的前三片段里有两个是无关的再强的 LLM 也救不回来。另一个隐蔽的坑是文档切分位置切断了表格或代码块导致语义不完整这类情况只能靠人工检查切片连续性没有银弹。5. 用评估集和成本漏斗守住 LLM 工程的质量下限5.1 搭一个 20 条的回归评估集每次改提示词都重跑LLM 工程的灾难往往不是“模型不行”而是“上一次能用的提示词改崩了却没人发现”。示例代码合集里如果没有评估脚本你自己应该补上。评估集不用大20 条覆盖典型场景就够关键是答案要人工标注。# evaluate.py evaluation_set [ {query: 你们的退款政策是什么, expected: 七天内免费退, category: policy}, {query: 发票怎么开, expected: 支持电子发票, category: billing}, {query: 订单号 12345 为什么还没发货, expected: 需要查物流状态, category: order}, ] for item in evaluation_set: answer, usage get_rag_answer(item[query]) hit item[expected] in answer print(f{item[category]}: {PASS if hit else FAIL} | tokens: {usage[total_tokens]})评估脚本的价值不在“准”而在“快”。改动任何 prompt 或参数后五分钟内跑完全量错误率对比能避免大量线上事故。expected字段不要求整句匹配只做子串包含判断即可因为 LLM 对同一语义的表达方式多种多样追求精确匹配只会徒增维护成本。第一个版本的评估集甚至可以用真实线上日志的用户反馈来构造不要一开始就追求大规模和自动化先跑起来最重要。5.2 成本与延迟的漏斗模型每次请求花在哪了LLM 工程的成本控制不是“省着用”三个字能解决的而是要知道钱花在哪一环节。一次 RAG 请求的 token 消耗分布通常是检索过程基本不花钱embedding 费用极低重排和生成才是大头。我按经验做了一个拆解一次请求消耗拆分 输入 promptsystem 检索片段 历史消息约 1200 tokens 模型输出最终回答约 400 tokens 重排调用约 200 tokens total 1800 tokens/请求按这个预算如果单日请求量是 10 万次使用gpt-3.5-turbo的成本可以粗略按1800 × 100000 × 单价计算。一旦发现成本超标优化的顺序是先压缩历史消息、再减少检索片段条数、最后考虑用更便宜的小模型做重排。这三个措施对回答质量的影响依次递减是成本优化的优先级参考。5.3 模型版本固定是工程实践的底线2023 年最容易出的生产事故之一是模型提供方悄悄更新了模型版本导致同一段 prompt 的输出完全变化。处理方式是显式固定模型版本号不要用gpt-3.5-turbo这种浮动别名升级模型时要在测试环境完整跑一遍评估集。代码里可以加一个MODEL_VERSION常量每次变更都会暴露在代码评审中从流程上阻止无声无息的升级。同时把每次请求的model字段写进日志排查问题时第一眼就能确认线上跑的是哪个版本。最后再分享一个细节temperature和top_p不要同时调整官方建议是保持一个固定、只调另一个两个一起动会让输出概率分布完全不可预测这是连资深工程师都会栽的坑。本文还有配套的精品资源点击获取
返回列表