LLM工程实例代码合集:从微调到RAG与Agent的完整基线
发布时间:2026/9/15 19:08:13
分类:文化教育
浏览:1234

简介一份聚焦2023年大模型与生成式人工智能工程实践的实例代码合集围绕ChatGLM模型调用、LangChain应用框架使用、ChatPDF文档问答实现、向量数据库接入以及StableDiffusion与Midjourney多模态图像生成五大专题展开适合有一定Python基础、希望快速上手大模型应用开发与智能体构建的开发者、算法工程师和研究者。压缩包共101个文件以Jupyter Notebook和Python脚本为主体便于分步阅读与复用同时包含样例数据、配置文件、说明文档和少量辅助资源整体约138.21MB目录按专题划分可快速定位所需模块。实例覆盖模型加载、提示设计、链路编排、向量检索到最终问答或图像生成的完整流程其中ChatPDF部分演示了长文档切分、向量化、相似度检索与答案生成向量数据库部分展示主流数据库接入方式可与LangChain协同搭建知识库问答系统。已有340人学习对初学者建立大模型应用认知、进阶者拓展落地思路均有参考价值。1. 一份实例代码合集看懂 2023 年的 LLM engineering 工作方式在 2023 年大型语言模型LLM的工程化产物还没有统一的交付形态于是大量项目以「LLM、AIGC、实例代码合集」这样一个 zip 包在团队、社群和课程里流转。解压之后里面通常是 Hugging Face 生态的训练与推理脚本、LangChain 时代的编排代码、几十个 prompt 模板外加部署用的配置。这类包对正在走 LLM 学习路线的人尤其有价值模型怎么加载、参数怎么传、数据格式长什么样、失败日志输出什么都是现成的。它把 LLM 原理落成了一条可以照着走的路。这里按这类包的常见组织方式拆一遍从模块划分、最小闭环到工程化改造给你一套能直接改成自己项目的 LLM engineering 基线。2. LLM engineering 实例代码合集技术栈拆解与目录结构2.1 代码合集里的六大模块数据、微调、推理、检索、智能体、评估先别急着跑代码看目录。2023 年的 LLM engineering 代码包绝大多数不会只有一个main.py而是按“模型能力从哪里来、到哪里去”分块。下面这六块是最常见的组合模块典型文件2023 年的常见实现数据管线build_dataset.py、format_instruction.pyHugging Facedatasets自写 JSONL 清洗脚本微调train_lora.py、train_sft.pytransformerspeft单卡 4bit 量化推理封装predict.py、server.pypipeline起步正式一点用 FastAPI检索增强build_index.py、rag_query.pyLangChain FAISS / Chroma智能体编排agent_executor.py、tools.pyLangChain Agent、ReAct 模板评估eval_*.py、judge.pyROUGE/BERTScore或 LLM-as-judge这六块不是并列的 demo而是一条流水线数据决定能力上界微调把通用模型往垂域拉推理服务决定线上怎么被调用检索和智能体回答“知识不够新、任务不够单步”的问题评估负责在每次改动后给出能不能上线的结论。一个代码合集如果六样齐基本就是一套完整的 LLM engineering 骨架只有其中两三样大概率是某个具体场景的补充材料。2.2 LLM 框架选型Transformers、LangChain 与轻量管线2023 年的实例代码里最常见的框架组合是transformers peft LangChain偶尔出现deepspeed或vLLM。transformers承担模型加载和生成peft承担低成本微调LangChain 承担 prompt engineering 里的模板拼装、检索增强和智能体编排。后来不少人批评 LangChain 抽象太重但抽象确实有收益它把分段式 prompt、工具调用、记忆、索引这些高频写法统一成了接口对刚转 LLM 工程的团队来说比从零维护一套工具调度快得多。llm-starter/ ├── data/ # 原始与清洗后的指令数据 ├── train/ # LoRA/SFT 微调脚本与参数 ├── infer/ # 单条推理与 HTTP 封装 ├── rag/ # 向量库构建与检索问答 ├── agent/ # agent 工具定义与编排 ├── eval/ # 自动评估脚本与样例数据 └── requirements.txt # 版本依赖清单这种目录结构本身就在表达工程分工。拿到这类包第一件事不是看模型名而是看requirements.txt和train/下有没有配置文件因为模型的加载方式在 2023 年内变了好几次Llama 既要求 transformers 版本匹配又依赖 tokenizer 的特殊文件配错版本第一个小时全耗在跑环境上。下面这段是那一代代码里最常见的“用量榜”写法直接读模型再生成# infer/quick_start.py from transformers import AutoModelForCausalLM, AutoTokenizer checkpoint meta-llama/Llama-2-7b-chat-hf # 换成你有权限访问的权重 tokenizer AutoTokenizer.from_pretrained(checkpoint) model AutoModelForCausalLM.from_pretrained(checkpoint, device_mapauto) prompt 把这句话改写成正式的会议邀请明天下午开会。 messages [{role: user, content: prompt}] inputs tokenizer.apply_chat_template( messages, return_tensorspt, add_generation_promptTrue ).to(model.device) outputs model.generate( inputs, max_new_tokens256, # 只限制新生成长度避免重复计算 prompt do_sampleTrue, temperature0.7, # 越大越随机线上服务一般 0.2~0.7 top_p0.9, ) print(tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokensTrue))apply_chat_template是 2023 年中开始普及的写法模型自带chat_template时system/user/assistant 的格式拼装统一交给 tokenizer不再手写### Human:这类容易出错的前缀。inputs.shape[1:]切片是因为 generate 返回的序列头部还包含输入 token要先把 prompt 部分切掉再解码。这段代码值得背下来它是那一代 LLM engineering 的最小公因数加载、拼 prompt、生成、解码四个动作。2.3 从 textcnn 到 LLM意图识别在实例代码里的迁移写法代码合集的“实例”价值很大程度体现在同一个问题换 LLM 之后代码长什么样。以意图识别为例textcnn 时代要定标签体系、标注数据、训练、调阈值BERT 时代要加CLS输出层、处理类别不平衡到了 LLM常见代码直接把意图体系写进 system prompt 做零样本分类# infer/intent.py INTENT_SCHEMA 你是客服分流器。只输出以下分类之一 book-预订, query_status-查进度, cancel-取消, refund-退款, others-其他。 分类不明就是 others。 def classify_intent(text: str) - str: response chat_model.invoke([ {role: system, content: INTENT_SCHEMA}, {role: user, content: text}, ]) return response.content.strip() print(classify_intent(帮我看看昨天买的手机到哪了)) # query_status这个迁移带来的变化值得说透。分类边界的修改成本从“重新训练”降为“改 prompt”所以实例代码里的INTENT_SCHEMA会被设计成常量而不是写死在业务函数里。少数类不再靠过采样而是靠 prompt 里的 few-shot 例子控制加两三个例子recall 往往就能拉上来。但延迟和成本都上升所以 2023 年下半年开始出现“小模型拦截 LLM 兜底”的混合方案。如果你在实例代码里看到fallback_model或fast_path这类命名就是这种 hybrid 架构的直接体现。3. 把代码合集跑成最小闭环数据清洗、LoRA 微调与模型封装3.1 垂域LLM数据准备JSONL、system prompt 与清洗脚本2023 年的微调实例里数据入口基本统一是 JSONL每行一个对话样本。字段名各家不同但核心三件套是system、instruction、output或者query和response。{system: 你是物流客服回答不超过50字, instruction: 用户想改收货地址, output: 请提供订单号和新的收货地址我马上为您修改。}代码合集的data/里通常会有一个clean_instruction.py把原始导出的噪声数据过滤成上面的格式。下面是一个带四项过滤的版本短回答、URL、营销词、多余空白。# data/clean_instruction.py import json import re def clean_example(ex): text ex[instruction] \n ex[output] if re.search(rhttps?://|www\., text): return None # 夹带链接的样本质量难保证 if len(ex[output].strip()) 4: return None # 回答太短微调不出信息量 if any(w in text for w in (【广告】, 加微信, 点击领取)): return None # 营销类噪声 ex[instruction] ex[instruction].strip() ex[output] ex[output].strip() return ex with open(data/raw.jsonl, encodingutf-8) as f: items [json.loads(line) for line in f if line.strip()] cleaned [] for ex in items: if clean_example(ex): cleaned.append(clean_example(ex)) with open(data/train_clean.jsonl, w, encodingutf-8) as f: for ex in cleaned: f.write(json.dumps(ex, ensure_asciiFalse) \n) print(f保留 {len(cleaned)} / {len(items)} 条)过滤条件的取舍和下游任务有关检索类任务要求文本完整URL 过滤要放宽客服类任务要求对话颗粒度短output下限可以收紧到 4 个字。这类脚本容易被忽略的价值是“可解释删了什么”。跑完打印保留比例原始数据的噪声率往往在 20% 以上先把数据洗干净比换更大的模型更划算。3.2 单卡 LoRA 微调 LLM 的最小训练脚本2023 年的实例代码里微调这一环最统一的做法是 LoRA 4bit 量化。单卡放不下全量参数而 LoRA 只训练插入注意力层的低秩矩阵显存占用小效果又远好于只改 prompt。下面是能直接改路径跑的最小训练脚本# train/train_lora.py from datasets import load_dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training model_name Qwen/Qwen-7B-Chat # 按显存换 1.8B/4B 版本 tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_name, load_in_4bitTrue, # 依赖 bitsandbytes单卡 24G 可跑 7B 级模型 device_mapauto, trust_remote_codeTrue, ) model prepare_model_for_kbit_training(model) lora_cfg LoraConfig( r8, # 秩越大可学信息越多显存和过拟合风险也越大 lora_alpha16, # 缩放系数一般取 2*r lora_dropout0.05, target_modules[q_proj, k_proj, v_proj, o_proj], task_typeCAUSAL_LM ) model get_peft_model(model, lora_cfg) def format_fn(examples): texts [] for s, i, o in zip(examples[system], examples[instruction], examples[output]): texts.append(tokenizer.apply_chat_template( [ {role: system, content: s}, {role: user, content: i}, {role: assistant, content: o}, ], tokenizeFalse )) return {text: texts} dataset load_dataset(json, data_filesdata/train_clean.jsonl)[train] dataset dataset.map(format_fn, batchedTrue) args TrainingArguments( output_dirout/lora-qwen, per_device_train_batch_size4, gradient_accumulation_steps8, # 等效 batch 4 * 8 32 learning_rate2e-4, # LoRA 的 lr 通常比全量微调高一个量级 num_train_epochs3, logging_steps10, save_strategyepoch, fp16True, report_tonone, ) trainer Trainer(modelmodel, argsargs, train_datasetdataset) trainer.train()改动最频繁的参数是target_modules。不同模型注意力层命名不一样Llama 系常见q_proj/v_projChatGLM 系偏query_key_value如果脚本跑起来提示模块名没匹配上说明 LoRA 实际插入的是零个目标层训练等于空转这是显式列出target_modules最大的坑。r的经验值是 8 起步领域数据量足够大再上 16 或 32lora_alpha保持约等于2*r可以让初始缩放不至于压爆 loss。训练时盯住loss和eval_loss两条曲线后者在第二个 epoch 后反弹先降learning_rate别急着加数据。3.3 推理服务封装与生成参数表微调完的产物是 adapter 权重实例代码里最常见的做法是用PeftModel把 adapter 合并回原模型再封装成 HTTP 服务。下面是最短可用的 FastAPI 版本# infer/api.py from fastapi import FastAPI from pydantic import BaseModel from peft import PeftModel from transformers import AutoModelForCausalLM, AutoTokenizer app FastAPI() model_name Qwen/Qwen-7B-Chat tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) base_model AutoModelForCausalLM.from_pretrained(model_name, device_mapauto) model PeftModel.from_pretrained(base_model, out/lora-qwen/checkpoint-xxx) class GenRequest(BaseModel): prompt: str max_new_tokens: int 512 temperature: float 0.7 top_p: float 0.9 app.post(/generate) def generate(req: GenRequest): inputs tokenizer(req.prompt, return_tensorspt).to(model.device) outputs model.generate( **inputs, max_new_tokensreq.max_new_tokens, temperaturereq.temperature, top_preq.top_p, do_samplereq.temperature 0, ) return {text: tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:])}上线前的生成参数建议按下面这张表在测试集上先过一遍而不是凭手感填参数常用区间调高后果调低后果max_new_tokens128~1024长文更完整但更慢、更贵截断风险增加temperature0.1~0.9表达更发散接近贪心重复变多top_p0.8~0.95词表选择面更大输出更死板repetition_penalty1.02~1.15重复被压制文本更破碎长文容易复读其中do_sample在temperature和top_p都为默认值时要显式打开否则部分实现会忽略采样参数。这类细节正是实例代码合集的核心价值2023 年的坑基本集中在“模板拼错、采样没开、device 没对齐”三件事上代码合集相当于把这些前人踩过的点按固定写法锁住了。4. 再进一步RAG、LLM Agent 与 AIGC 输出质量控制4.1 RAG 实例代码向量检索、上下文注入与带来源回答微调解决“说话风格和领域口径”问题RAG 解决“知识不够新、答案没有依据”问题。2023 年的 AIGC 工程代码里 RAG 已经标配化核心三段是切块建索引、检索 top-k、把上下文注入 prompt。# rag/rag_query.py from langchain.vectorstores import FAISS from langchain.embeddings import HuggingFaceEmbeddings embedder HuggingFaceEmbeddings(model_nameBAAI/bge-large-zh-v1.5) db FAISS.load_local(index/wiki, embedder) question 退款多久能到账 docs db.similarity_search_with_score(question, k4) passages [] for doc, score in docs: if score 1.0: # 距离阈值具体值视 embedder 而定 passages.append(doc.page_content) context \n\n.join(passages) prompt f只依据下面资料回答不要编造\n{context}\n\n问题{question}这里的k4是最常调的参数调太大prompt 被无关段落带偏调太小召回可能漏关键句子。搭配阈值过滤也很重要距离高于阈值的段落宁可丢。常见做法是先建 50 条测试问题人工看检索命中率再定k。另外LangChain 在 2023 年底开始拆包langchain.vectorstores这套导入在新版要改成langchain_community.vectorstores如果 requirements 里锁的是旧版本升级框架时这一行得同步改。4.2 基于 llm agent 的编排代码工具调用与任务分解比 RAG 更进一步的是 llm agent。代码合集里的 agent 示例本质上是给模型一张“会什么工具”的清单让它在多轮里决定调用哪个、传什么参数、结果怎么拼回上下文。# agent/agent_executor.py from langchain.agents import create_react_agent, AgentExecutor from langchain.tools import Tool from langchain_core.prompts import PromptTemplate def query_orders(sql: str) - str: 执行只读 SQL返回第一行结果用于查订单。 return run_readonly_sql(sql) # 业务层已实现的只读查询函数 tools [ Tool( namequery_orders, funcquery_orders, description查询订单信息参数为只读 SQL, ) ] prompt PromptTemplate.from_template( 你是订单助手。请根据工具描述选择工具并用中文回答。\n\n{input}\n\n{agent_scratchpad} ) agent create_react_agent(llm, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue) executor.invoke({input: 昨天一共多少笔订单})写 agent 工具最容易翻车的点是 description 写得太笼统。query_orders的 description 必须说清“参数是什么、返回什么”模型才可能生成正确调用。另一个坑是 ReAct 类 agent 是多轮推理每轮都可能调用工具所以要控制迭代上限否则一次请求能打十几个调用费用和延迟都不可控。相关参数可以这样设参数建议值说明max_iterations5~8超过后强制返回已有结论early_stopping_methodgenerate让模型自行判断是否结束handle_parsing_errorsTrue输出解析失败时给模型一次修正机会4.3 AIGC 检测与输出质量评估把检测器当成指标代码合集的评估目录里2023 年后半段频繁出现两个东西一个是基于规则的输出校验一个是 LLM-as-judge。AIGC 检测在工程这里的定位不是针对个人写作的查重而是企业内容发布前的质量门禁回答是否包含不适宜对外发布的表述、是否有虚构来源、格式是否合规。最稳的做法是把检测器结果当作一个可量化指标接入评估流程# eval/judge.py JUDGE_PROMPT 判断下面回答是否安全、准确、可读。返回 JSON {score: 0-10, risky: false, reason: 一句话说明} def evaluate(prediction: str) - dict: result judge_model.invoke([ {role: system, content: JUDGE_PROMPT}, {role: user, content: prediction}, ]) import json return json.loads(result.content) sample 退款会在 1-3 个工作日原路返回。 print(evaluate(sample))给 judge 模型固定temperature0评估任务要可复现不需要随机性。AIGC 检测器误伤也常见有些内容因为句式规整被标成高概率 AI 生成。工程上的处理方式是调整业务侧的输出约束比如强制增加数字来源、把回答长度压短、在 prompt 里指定“分点说明”。这些手段是为了让输出更贴合业务风格而不是为了逃避检测这个边界要分清楚。5. 把实例代码改成自己的工程基线复现与验证技巧拿到一份LLM-aigc-实例代码合集.zip最难的不是读懂某个文件而是让它在你的环境里稳定复现。这里分享一套固定动作专门对付 2023 年这一代 LLM 代码的版本漂移问题。先建一个冒烟测试目录只跑最不依赖 GPU 的路径加载 tokenizer、读数据、跑一次生成断言输出非空。所有相关脚本用pytest标记为smoke每次改环境先执行python -m pytest -m smoke -x。这一步能过滤掉七成“环境装好了但模型路径不对”的问题。然后是依赖锁定。requirements.txt只写transformers4.30这类范围约束对 LLM 代码来说太宽因为 transformers 小版本之间行为都会变。我会在干净环境里安装后立刻把完整版本号导出成锁文件python -m pip freeze requirements.lock python -m pytest tests/smoke -x python scripts/check_data.py --path data/train_clean.jsonl --schema schema.jsoncheck_data.py做三件事字段名是否齐全、output有没有空值、样本数是否和源数据对得上。数据校验脚本放在代码库里比写在说明文档里更容易被下一个人复用。复现清单建议固定成一张表检查项做法通过标准模型权重记录config.json的 hash与发布说明一致LoRA adapter记录 checkpoint 目录 metadata加载后model.peft_config有值推理结果固定 20 条 golden case与基线输出 diff 不超 5 条数据版本记录 JSONL 行数与 md5行数、md5 均一致最后一个保留技巧每个训练脚本的日志里记录数据集 md5这样模型卡和训练日志能对得上。判断代码是否真的可复现不看“跑通”而看同一份输入上是否产出同一份输出生成任务退一步看评估分布是否一致。等eval/metrics.json连续三个版本都达到基线之上一档再谈放开并发在那之前把模型、数据、代码三层 hash 打在同一份发布说明里就好。本文还有配套的精品资源点击获取