🧊 冻冻智能数据分析助手基于 LangChain + LM Studio 的 语义层 NL2SQL 数据分析系统，支持用自然语言查询业务数据，自动生成 SQL、可视化图表和分析结论。git地址https://github.com/zxliucn/Datas_Agent目录项目概述技术架构项目结构核心模块4.1 主入口 (app.py)4.2 意图解析 (core/intent_parser.py)4.3 SQL 构建 (core/sql_builder.py)4.4 SQL 执行 (core/sql_executor.py)4.5 图表生成 (core/chart_generator.py)4.6 洞察生成 (core/insight_generator.py)查询模板系统 (templates)Web 界面 (web)工具模块 (utils)配置说明数据库表结构快速开始API 接口项目预览1. 项目概述冻冻智能数据分析助手是一个基于语义层 NL2SQL 架构的数据分析系统，采用 预定义模板 + LLM 意图解析 的安全方案，避免 LLM 直接生成 SQL 带来的安全风险。核心特性功能说明🔍 自然语言查询输入自然语言问题，自动解析意图并生成 SQL📊 可视化图表根据查询类型自动选择折线图、柱状图、饼图等💡 智能洞察从查询结果中提取关键结论和业务建议🔒 安全可控预定义 SQL 模板，LLM 仅负责意图解析，不直接生成 SQL🌐 Web 界面基于 Gradio 的可视化交互界面支持的查询类型查询类型示例问题商品销量虾滑卖了多少？上个月牛筋的销售额商品排行这个月哪个商品卖得最好？销量前10订单趋势最近7天的订单趋势订单列表待发货的订单有哪些？客户排行哪个客户花钱最多？库存预警库存不足的商品售后排行退款最多的商品经营概况本月经营情况2. 技术架构┌─────────────────────────────────────────────────────────┐ │ Gradio Web UI │ │ 输入自然语言问题 → 展示图表和洞察结论 │ └──────────────────────┬──────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ NL2SQL 主控制器 │ │ 意图解析 → SQL 构建 → 执行 → 可视化 │ └──────────────────────┬──────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │ IntentParser │ │ SQLBuilder │ │ SQLExecutor │ │ LLM 意图解析 │ │ 模板拼装 │ │ MySQL 查询 │ └──────────────┘ └────────────┘ └──────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │ChartGenerator│ │ InsightGen │ │ QueryTemplates│ │ 图表可视化 │ │ 洞察分析 │ │ SQL 模板库 │ └──────────────┘ └────────────┘ └──────────────┘设计思路采用 语义层 NL2SQL 方案，而非让 LLM 直接生成 SQL：LLM 只做意图解析：理解用户自然语言，输出结构化的 {intent, params} JSONSQL 由模板拼装：每种查询类型对应预定义的 SQL 模板，安全可控参数映射处理：通过 param_mapping 将自然语言参数（如"上个月"）转换为 SQL 条件优势：✅ 安全：杜绝 SQL 注入风险✅ 可控：SQL 逻辑可审查、可维护✅ 稳定：不依赖 LLM 生成正确 SQL 的能力✅ 可扩展：新增查询类型只需添加模板3. 项目结构Datas_agent/ ├── app.py # 主入口，启动服务 ├── config.py # 配置文件（数据库、LLM） ├── .env # 环境变量 ├── requirements.txt # 依赖清单 │ ├── core/ # 核心业务模块 │ ├── __init__.py │ ├── intent_parser.py # LLM 意图解析 │ ├── sql_builder.py # SQL 模板拼装 │ ├── sql_executor.py # SQL 执行器 │ ├── chart_generator.py # 图表生成（Plotly） │ └── insight_generator.py # 洞察分析生成 │ ├── templates/ # SQL 查询模板 │ ├── __init__.py │ └── query_templates.py # 8 种查询模板定义 │ ├── web/ # Web 界面 │ ├── __init__.py │ └── gradio_app.py # Gradio 界面 │ ├── utils/ # 工具模块 │ ├── __init__.py │ └── logger.py # 日志与计时 │ └── preview/ # 预览截图 ├── p1.png └── p2.png4. 核心模块4.1 主入口 (app.py)NL2SQL 主控制器，串联整个处理流程：class NL2SQL: def __init__(self, llm): self.intent_parser = IntentParser(llm) # LLM 意图解析 self.sql_builder = SQLBuilder() # SQL 模板拼装 def convert(self, question): # 1. 解析意图 → {intent, params} result = self.intent_parser.parse_with_defaults(question) # 2. 构建 SQL sql = self.sql_builder.build(intent, params) return intent, sql, params启动流程：初始化 LLM → 创建各模块 → 测试数据库连接 → 启动 Gradio Web 界面（端口 7861）4.2 意图解析 (core/intent_parser.py)使用 LLM 将自然语言问题解析为结构化的意图和参数。处理流程：将用户问题 + 预定义的意图列表作为 Prompt 发送给 LLMLLM 返回 JSON 格式的 {intent, params}提取 JSON 并解析，失败则返回 unknown调用 parse_with_defaults() 自动填充默认参数Prompt 策略：提供 8 种查询类型的触发词、参数说明和示例强制 JSON 输出格式使用 %s 占位符避免 format 冲突输出示例：// 输入："上个月虾滑卖了多少" {"intent": "product_sales", "params": {"product": "虾滑", "time_range": "last_month"}} // 输入："销量前10的商品" {"intent": "product_ranking", "params": {"top_n": 10}}4.3 SQL 构建 (core/sql_builder.py)根据意图和参数，从预定义模板拼装 SQL。构建流程：从模板库获取 SQL 模板应用默认值（如 limit=10, time_range=this_month）通过 param_mapping 映射参数（如将 time_range 转换为 AND DATE(o.order_time) = ...）填充模板占位符清理 SQL（修复空 WHERE、多余 AND 等边界情况）4.4 SQL 执行 (core/sql_executor.py)基于 SQLAlchemy + PyMySQL 执行 SQL 查询。仅允许 SELECT 查询，拒绝写操作返回 pandas DataFrame启动时自动测试数据库连接4.5 图表生成 (core/chart_generator.py)基于 Plotly 根据查询意图自动选择图表类型：意图类型图表类型说明order_trend折线图时间趋势，支持多指标product_ranking柱状图排行榜，限显前 15 条customer_ranking柱状图（绿色）客户消费排行stock_warning柱状图（红色）库存预警after_sale_ranking柱状图（橙色）售后排行product_salesMarkdown 指标卡单商品销量详情business_overviewMarkdown 表格经营概况其他自动判断饼图/柱状图/折线图4.6 洞察生成 (core/insight_generator.py)从查询结果中提取关键结论和业务建议，输出 Markdown 格式。分析维度：商品排行：Top3、总销量、集中度分析（Top3 占比）订单趋势：日均数据、升降趋势判断、峰值日期客户排行：VIP 客户识别、人均消费库存预警：最紧急商品、按分类统计、补货建议售后排行：退款率分析、改进建议经营概况：核心指标表格、退款率评级5. 查询模板系统 (templates)query_templates.py 定义了 8 种查询模板，每种模板包含：字段说明templateSQL 模板字符串，{placeholder} 占位required_params必填参数列表optional_params可选参数列表param_mapping参数到 SQL 条件的映射函数defaults默认值字典时间范围转换：支持 today、yesterday、this_week、this_month、last_month、last_7_days、last_30_days，自动转换为 MySQL 日期函数。参数映射函数示例：# 时间范围 → WHERE 条件 "time_range": lambda x: f"AND {get_time_condition(x)}" # 商品分类 → 分类过滤 "category": lambda x: f"AND p.category = '{x}'" # 经营概况 → 同时返回时间条件和中文时间名（tuple） "time_range": lambda x: (get_time_condition(x), get_time_range_name(x))6. Web 界面 (web)基于 Gradio 的交互式 Web 界面。界面布局：输入区：文本框 + 开始分析 / 清空按钮SQL 展示：代码块展示生成的 SQL图表区：Plotly 交互式图表洞察区：Markdown 格式的分析结论内置示例问题：提供各查询类型的示例问题，引导用户使用。7. 工具模块 (utils)日志模块 (logger.py)info / debug / error / warn 四级日志@timing 装饰器：记录函数执行耗时通过 APP_CONFIG['debug'] 控制是否输出 DEBUG 级别日志8. 配置说明环境变量 (.env)# MySQL 数据库 MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USER=your_username MYSQL_PASSWORD=your_password MYSQL_DATABASE=your_database # LM Studio 本地模型 # base_url: LM Studio API 地址 # model: 使用的模型（如 qwen/qwen3.5-9b） # temperature: 0（意图解析需要确定性输出）应用配置 (config.py)LM_STUDIO_CONFIG = { 'base_url': 'http://127.0.0.1:1234/v1', 'model': 'qwen/qwen3.5-9b', 'temperature': 0, 'max_tokens': 512, } APP_CONFIG = { 'default_limit': 100, 'max_limit': 1000, 'debug': True, }9. 数据库表结构系统依赖以下 MySQL 表：表名说明关键字段products商品表product_id, name, category, price, stockorders订单表order_id, user_id, order_time, total_amount, order_statusorder_items订单明细表order_id, product_id, product_name, quantity, total_priceusers用户表id, username, nicknameafter_sale_item售后表order_id, product_name, refund_num, refund_amount10. 快速开始安装依赖pip install -r requirements.txt主要依赖：langchain-openai — LLM 调用（兼容 OpenAI API 格式）gradio — Web 界面plotly — 图表可视化pandas — 数据处理sqlalchemy + pymysql — 数据库连接python-dotenv — 环境变量管理配置启动 LM Studio 并加载模型（如 Qwen3.5-9B），确保 API 服务运行在 http://127.0.0.1:1234/v1修改 .env 文件中的 MySQL 连接信息启动服务python app.py启动成功后访问：http://localhost:7861试试这些问题虾滑卖了多少？ 这个月哪个商品卖得最好？ 最近7天的订单趋势 待发货的订单有哪些？ 哪个客户花钱最多？ 库存不足的商品 退款最多的商品 本月经营情况11. API 接口系统通过 Gradio 提供交互式 Web 接口（端口 7861），无需额外 API 调用。直接在浏览器中访问即可使用。如需集成到其他系统，可参考 NL2SQL.convert() 方法封装为 REST API：from app import NL2SQL from core import SQLExecutor nl2sql = NL2SQL(llm) executor = SQLExecutor() intent, sql, params = nl2sql.convert("虾滑卖了多少") df = executor.execute(sql)12. 项目预览系统提供基于 Gradio 的 Web 数据分析界面，输入自然语言即可获得 SQL、图表和洞察结论。界面预览![数据分析界面-主视图]图 1：智能数据分析助手主界面 — 自然语言输入与结果展示![数据分析界面-功能展示]图 2：查询结果展示 — SQL 语句、可视化图表与分析结论总结特性实现方式🔒 安全可控语义层模板方案，LLM 仅解析意图，不直接生成 SQL🧠 智能解析LangChain + LM Studio 本地大模型📊 自动可视化Plotly 按意图自动匹配图表类型💡 业务洞察基于查询结果生成结构化分析结论🚀 快速扩展新增查询类型只需添加模板，无需修改 LLM 逻辑
项目结构llamaindex-rag-example/├── data/ # 存放待处理的原始文档（如PDF/TXT/Markdown等）├── storage_exp/ # 持久化存储向量索引的目录（自动生成）├── 01_build.py # 构建并持久化向量索引的脚本└── 02_query.py # 加载索引并进行问答查询的脚本PDF文件转Markdown#如果直接用 pdf版解析会很差，可以用mineru转化为md文件，但是测试 api 速度也不快， 可以直接在官网将文档转化下载，时间最快，还可以进行调整 官网：https://mineru.net/ 01_build.py 构建索引import os import time import torch from pathlib import Path from llama_index.core import VectorStoreIndex, Settings, Document from llama_index.embeddings.huggingface import HuggingFaceEmbedding from llama_index.core.node_parser import SimpleNodeParser # 配置 device = "cpu" #如果直接用 pdf版解析会很差，可以用mineru转化为md文件，但是测试 api 速度也不快， 可以直接在官网将文档转化下载，时间最快，还可以进行调整https://mineru.net/ markdown_path = "./data/test_manual.md" persist_dir = "./storage_exp" # 创建目录 os.makedirs(persist_dir, exist_ok=True) os.makedirs("./models", exist_ok=True) # 2. 配置全局 Embedding（本地 Qwen3 模型） EMBEDDING_PATH = "/Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418" Settings.embed_model = HuggingFaceEmbedding( model_name=EMBEDDING_PATH, trust_remote_code=True ) # 读取 Markdown 文件 print(f"读取 Markdown: {markdown_path}") with open(markdown_path, "r", encoding="utf-8") as f: markdown_content = f.read() print(f"文档长度: {len(markdown_content)} 字符") # 创建 Document doc = Document( text=markdown_content, metadata={ "source": Path(markdown_path).name, "file_path": str(Path(markdown_path).absolute()), } ) # 切分文档 print("切分文档...") parser = SimpleNodeParser(chunk_size=512, chunk_overlap=50) nodes = parser.get_nodes_from_documents([doc]) print(f"生成 {len(nodes)} 个节点") # 构建索引 print("构建索引...") start_time = time.time() index = VectorStoreIndex(nodes) build_time = time.time() - start_time print(f"索引构建完成，耗时 {build_time:.2f} 秒") # 保存索引 print(f"保存索引到 {persist_dir}...") index.storage_context.persist(persist_dir=persist_dir) # 统计 index_size = 0 for root, dirs, files in os.walk(persist_dir): for file in files: index_size += os.path.getsize(os.path.join(root, file)) print("\n" + "="*50) print("构建完成!") print("="*50) print(f"文档长度: {len(markdown_content):,} 字符") print(f"节点数量: {len(nodes)}") print(f"索引大小: {index_size / 1024:.1f} KB") print(f"总耗时: {build_time:.2f} 秒") print("="*50) 02_query.pyimport os import sys import time from llama_index.core import StorageContext, load_index_from_storage, Settings from llama_index.llms.openai import OpenAI from llama_index.llms.openai_like import OpenAILike from llama_index.core.prompts import PromptTemplate from llama_index.core.postprocessor import SentenceTransformerRerank from llama_index.retrievers.bm25 import BM25Retriever # 导入依赖 from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings from llama_index.embeddings.huggingface import HuggingFaceEmbedding import os from dotenv import load_dotenv # 加载环境变量 load_dotenv() QIANWEN_KEY = os.getenv("QIANWEN_API_KEY") os.environ["TOKENIZERS_PARALLELISM"] = "false" # 配置 device = "mps" persist_dir = "./storage_exp" lm_studio_url = "http://localhost:1234/v1" # 2. 配置全局 Embedding（本地 Qwen3 模型） EMBEDDING_PATH = "/Users/t-mac/.cache/huggingface/hub/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418" Settings.embed_model = HuggingFaceEmbedding( model_name=EMBEDDING_PATH, trust_remote_code=True ) # 1. 配置全局 LLM（通义千问） Settings.llm = OpenAILike( model="qwen3.5-flash", api_base="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key=QIANWEN_KEY, is_chat_model=True, temperature=0.1 ) # 重排序器 # 中文专用重排模型 # reranker = SentenceTransformerRerank( # model="BAAI/bge-reranker-base", # top_n=5 # ) # 提示词模板 PROMPT = PromptTemplate( """你是一个专业的 HR 助手，请基于以下员工手册内容回答问题。 {context_str} 问题：{query_str} 回答：""" ) # 加载索引 print("加载索引...") storage_context = StorageContext.from_defaults(persist_dir=persist_dir) index = load_index_from_storage(storage_context) print(f"索引加载成功，共 {len(index.docstore.docs)} 个节点") def hybrid_search(query, top_k=5): """混合检索""" # 向量检索 vector_retriever = index.as_retriever(similarity_top_k=10) vector_nodes = vector_retriever.retrieve(query) # BM25 检索 all_nodes = [] for doc_id in index.docstore.docs.keys(): doc = index.docstore.get_document(doc_id) if hasattr(doc, 'nodes'): all_nodes.extend(doc.nodes) else: all_nodes.append(doc) bm25_retriever = BM25Retriever.from_defaults( nodes=all_nodes, similarity_top_k=10, ) bm25_nodes = bm25_retriever.retrieve(query) # 合并去重 seen = set() merged = [] for node in vector_nodes + bm25_nodes: if node.node.node_id not in seen: seen.add(node.node.node_id) merged.append(node) return merged[:top_k] # # 重排序 # reranked = reranker.postprocess_nodes(merged, query_str=query) # return reranked[:top_k] def generate_answer(query, nodes): """生成回答""" from llama_index.core import get_response_synthesizer synthesizer = get_response_synthesizer(text_qa_template=PROMPT) return synthesizer.synthesize(query, nodes=nodes) def query(question): """执行查询""" nodes = hybrid_search(question) response = generate_answer(question, nodes) return response.response if __name__ == "__main__": question = "员工违约的条件有哪些？" print(f"\n问题: {question}") print("-" * 40) start = time.time() answer = query(question) elapsed = time.time() - start print(f"回答: {answer}") print("-" * 40) print(f"耗时: {elapsed:.2f} 秒") 测试结果/opt/miniconda3/envs/langchain-dev/bin/python /Users/t-mac/workspace/Python/python_code/24-LlamaIndex/projects/llamaindex-rag-example/02_query.py W0522 23:40:21.752000 33319 site-packages/torch/distributed/elastic/multiprocessing/redirects.py:35] NOTE: Redirects are currently not supported in MacOs. W0522 23:40:21.774000 33319 site-packages/torch/utils/_pytree.py:630] <enum 'KernelPreference'> is an Enum subclass and is now natively supported by torch.compile as an opaque value type. Calling register_constant() on Enum subclasses is deprecated and will be an error in a future release. W0522 23:40:21.793000 33319 site-packages/torch/utils/_pytree.py:630] <enum 'ScaleCalculationMode'> is an Enum subclass and is now natively supported by torch.compile as an opaque value type. Calling register_constant() on Enum subclasses is deprecated and will be an error in a future release. Loading weights: 100%|██████████| 310/310 [00:00<00:00, 8048.99it/s] 加载索引... 索引加载成功，共 98 个节点 问题: 员工违约的条件有哪些？ ---------------------------------------- 回答: 你好！我是你的专业 HR 助手。根据提供的《员工手册》内容，虽然手册中主要使用“违纪”、“严重违纪”及“解除劳动合同”等术语，但以下情况均属于员工违反公司规章制度或劳动合同约定的条件（即通常理解的“违约”情形）。 以下是基于文档整理的员工违纪及违规的具体条件分类： ### 1. 考勤与出勤违规 * **迟到早退：** 连续 30 天内迟到或早退三次及以上（10.1.1）；试用期内迟到早退累计达三次或以上（4.5）。 * **旷工：** * 未经请假或请假未获批准擅自不到岗（3.1）。 * 请假期满未续假或续假未批逾期不归（3.3.2）。 * 特殊原因未在当日上班 1 小时内请假，或事后 3 个工作日内未补办手续（3.3.3）。 * 不服从工作调动、工时制度调整拒不执行导致未正常出勤（3.3.4）。 * **后果阈值：** 连续旷工 2 天及以上，或一年内累计旷工 3 天及以上（10.1.2）；连续旷工 3 天及以上，或全年累计旷工 7 天及以上（3.2）。 * **打卡异常：** 委托他人打卡或替他人打卡（10.1.3）；连续 30 天累计 6 次以上未打卡且无法说明原因或提供证明（1.4 / 10.1.4）。 * **试用期出勤率：** 试用期实际出勤日/应出勤日低于 85%（4.5）。 ### 2. 诚信与入职信息违规 * **虚假信息：** 伪造学历、工作经历、证书，简历、入职登记信息、健康状况等与真实情况不符（4.6）。 * **隐瞒欺骗：** 有欺骗、隐瞒行为（4.6）。 * **价值观问题：** 诚信等价值观存在问题，无论服务年限长短，均可能导致立即解除劳动合同（1.3）。 * **竞业限制：** 入职本公司将违反竞业限制义务（4.15）。 ### 3. 行为规范与安全违规 * **扰乱秩序：** 发起、参与离岗、怠工、罢工、示威，或鼓动他人参与（10.2.1）。 * **骚扰行为：** 侮辱、诽谤、辱骂、造谣、威胁、恐吓他人（10.2.2）。 * **现场管理：** 将无关人员带入工作现场，情节严重或造成损害（10.2.3）。 * **安全隐患：** 在库房等高风险区域内吸烟，情节严重或造成损害（10.2.4）。 * **资产损害：** 损坏或遗失公司财物，浪费公司资产，情节严重或造成损害（10.2.5 / 10.2.6）。 ### 4. 法律、健康与个人资质违规 * **刑事犯罪：** 被判处有期徒刑尚在服刑（4.8）；通缉在案（4.10）；实施违反国家法律法规的行为（如加入邪教、恐怖组织、酗酒、吸毒、赌博、嫖娼等）（4.14）。 * **权利状态：** 被剥夺公民权利者（4.9）。 * **年龄与健康：** 未满 16 周岁（4.11）；传染性疾病病原携带者或患有其他严重疾病（4.12）；患有精神病（4.13）。 * **债务问题：** 拖欠他人重大债务或进入法院执行程序未偿还债务者（4.16）。 * **过往记录：** 曾经被公司辞退或未经批准擅自离职者（4.7）。 ### 5. 试用期特殊解除条件 * **处分记录：** 试用期期间受记过及以上处分者（4）。 * **综合表现：** 上述考勤、诚信、法律等条款在试用期内触犯者（4.5 - 4.16）。 ### 6. 管理与连带责任 * **监管不力：** 管理者对员工违纪行为存在监管不力、失察、包庇、纵容或教唆等情形的，主管也将受到处罚（9.6）。 * **兜底条款：** 其他任何与前述违纪行为严重性相当的行为，或违反部门规定的行为，情节较重或造成一般损害的（5）。 **总结：** 根据手册第 10 条，若员工出现上述**严重违纪行为**，公司将对其进行**辞退（解除劳动合同）**。其中涉及诚信价值观问题、刑事犯罪、严重旷工等行为，公司有权立即解除合同且不支付经济补偿。 希望以上整理能帮助您准确理解员工手册中的相关规定。如有具体案例需要分析，欢迎随时提问。 ---------------------------------------- 耗时: 37.51 秒
Mac MLX 实战微调 Qwen3\-4B\-Instruct 客服模型完整教程
Mac MLX 实战微调 Qwen3-4B-Instruct 客服模型完整教程git地址https://github.com/zxliucn/Customer_Model_Fine-Tuning一、项目概述本项目基于 Apple MLX 框架，在 Mac 设备上轻量化微调 Qwen3-4B-Instruct 模型，针对冻冻网络冷冻食品客服场景进行专属适配。全程采用 LoRA 微调方案，仅训练少量参数，兼顾训练速度与效果，最终完成模型权重合并、GGUF 量化，可本地离线部署推理。核心场景：冷冻食品产品咨询、售后处理、物流查询、团购合作、会员问题等企业客服问答场景训练方案：MLX-LoRA 轻量化微调、Alpaca 数据格式、500 轮迭代训练最终产出：LoRA 微调权重、合并完整模型、Q4_K_M 量化 GGUF 模型二、完整项目结构标准化项目目录，分工明确，便于训练、权重管理与模型迭代：mlx3/ # 项目根目录 ├── adapters/ # LoRA 微调权重输出目录（迭代权重+最终权重） ├── data/ # 训练/验证数据集目录 │ ├── train.jsonl # 训练数据集 │ └── valid.jsonl # 验证数据集 ├── qwen3-4b-instruct/ # 原始 Qwen3-4B-Instruct 基座模型文件 ├── qwen3-4b-fused-iter500/ # 训练后合并的完整微调模型 ├── config.yaml # 训练核心超参数配置文件 └── train.sh # 一键启动训练脚本三、环境与模型准备3.1 依赖环境本项目基于 Mac MLX 框架，需提前安装对应依赖：pip install mlx-lm modelscope3.2 基座模型下载通过 modelscope 下载官方 Qwen3-4B-Instruct 2507 版本基座模型，自动保存至项目指定目录：modelscope download --model Qwen/Qwen3-4B-Instruct-2507 --local_dir ./qwen3-4b-instruct四、数据集准备采用标准 Alpaca 格式 JSONL 数据集，分为训练集和验证集，覆盖冷冻食品客服全场景问答，包含产品咨询、售后理赔、物流配送、会员权益、团购合作、食用指南、资质认证等场景。4.1 训练数据集 data/train.jsonl包含大量客服真实问答样本，用于模型微调学习，完整数据如下：{"prompt": "你是谁？", "completion": "您好，我是冻冻网络的智能客服助理。我们公司主营冷冻食品，包括鱼丸、虾滑、牛排、冰淇淋、预制菜等。请问有什么可以帮您的吗？"} {"prompt": "你们公司叫什么", "completion": "我们是冻冻网络，主营冷冻食品，比如鱼丸、虾滑、牛排、冰淇淋、预制菜等。需要我帮您推荐产品吗？"} {"prompt": "冻冻网络是干嘛的", "completion": "冻冻网络是一家冷冻食品公司，主要产品包括鱼丸、虾滑、牛排、冰淇淋、预制菜等。我们是专业的冷冻食品供应商，全程冷链配送~"} {"prompt": "你们卖什么", "completion": "我们主要卖冷冻食品哦~比如鱼丸、虾滑、牛排、冰淇淋、预制菜等等。您想了解哪一类？我可以给您详细介绍~"} {"prompt": "你是真人还是机器人", "completion": "我是冻冻网络的智能客服机器人~如果遇到我解决不了的问题，您可以拨打客服电话 400-xxx-886，转人工客服为您处理。"} {"prompt": "你们家的虾滑保质期多久？", "completion": "我们的虾滑在-18℃冷冻条件下保质期12个月。每包包装上都有具体生产日期哦~需要我帮您查某个批次的剩余保质期吗？"} {"prompt": "黑椒牛排辣不辣？小孩能吃吗？", "completion": "黑椒牛排是微辣口味，黑胡椒的香辣，小朋友可能不太适应。推荐您看看我们的原味牛排或者儿童鱼排，完全不辣，很受小朋友欢迎~"} {"prompt": "收到的鱼丸全化了，都软了！", "completion": "非常抱歉给您带来不好的体验！冷冻品在运输中化冻确实会影响品质。请您拨打我们的售后客服电话 400-xxx-886，提供订单号，我们会尽快为您核实并处理理赔。"} {"prompt": "我的订单怎么还没到？", "completion": "非常抱歉让您久等了。请您拨打我们的客服电话 400-xxx-886，提供订单号，我们会联系快递公司为您优先处理。"} {"prompt": "我不想要了，能退货吗？", "completion": "亲，冷冻食品属于特殊商品，非质量问题不支持无理由退货哦。如果您遇到化冻、破损等质量问题，请拨打售后电话 400-xxx-886，我们会按标准为您处理。"} {"prompt": "我是开烧烤店的，想大量买，有优惠吗？", "completion": "感谢您的关注！批量采购请联系我们的企业客服 400-xxx-886，会有专门的客户经理为您报价。或者您可以留下联系方式，我们安排专人联系您。"} {"prompt": "那个墨鱼丸怎么下架了？", "completion": "墨鱼丸目前库存紧张暂时下架了，预计下周二会补货~建议您关注店铺首页，上架会第一时间通知。或者看看我们的鱿鱼丸，口感也很不错哦~"} {"prompt": "你们家东西太差了！再也不买了！", "completion": "非常抱歉让您失望了。能具体说一下是哪个产品吗？我们会认真记录您的反馈。请您拨打客服电话 400-xxx-886，我们会安排专人联系您沟通解决方案。"} {"prompt": "你们公司在哪里？招人吗？", "completion": "我们公司在杭州市余杭区xxx科技园。招聘信息可以关注我们的官方公众号“冻冻网络”查看哦~请问还有其他冻品相关的问题需要帮助吗？"} {"prompt": "这个能吃吗？", "completion": "请问您指的是哪个产品呢？如果是我们的冻品，按照包装说明正确解冻和烹饪都是安全的。如果还是不确定，您可以拨打客服电话 400-xxx-886 进一步咨询。"} {"prompt": "你们家冰淇淋化了怎么办？", "completion": "非常抱歉给您带来不便！冰淇淋化冻会影响口感和品质。请您拨打我们的售后客服电话 400-xxx-886，提供订单号，我们会为您处理。"} {"prompt": "牛排保质期到什么时候？", "completion": "我们的牛排冷冻保质期12个月，具体保质期请以包装上的生产日期为准。方便提供批次号吗？我可以帮您查询。"} {"prompt": "有优惠券吗？", "completion": "当前优惠活动您可以查看店铺首页。如果有可用优惠券，结算时会自动显示。具体问题请拨打客服电话 400-xxx-886 咨询。"} {"prompt": "能不能快点发货？", "completion": "我们会尽力加快处理。您可以在订单页面查看物流信息，如有异常请拨打 400-xxx-886 联系我们。"} {"prompt": "发错货了，我要的是虾滑不是鱼丸", "completion": "非常抱歉给您添麻烦了！请您拨打售后电话 400-xxx-886，提供订单号和收到的产品照片，我们会为您安排换货或退款。"} {"prompt": "少发了一包，怎么办？", "completion": "很抱歉给您带来困扰。请您拨打客服电话 400-xxx-886，提供订单号和收货开箱视频/照片，我们会核实后补发或退款。"} {"prompt": "你们用的什么快递？", "completion": "我们主要使用顺丰冷链和京东冷链，具体以实际发货为准。需要查询您订单的物流信息，请拨打 400-xxx-886 并提供订单号。"} {"prompt": "可以指定送货时间吗？", "completion": "目前订单由快递公司配送，暂时无法精确指定时间。您可以在快递派送时联系快递员协商。如有特殊需求，请拨打 400-xxx-886 说明情况。"} {"prompt": "我想投诉配送员态度差", "completion": "非常抱歉给您带来不愉快的体验！我们会认真记录您的投诉。请您拨打客服电话 400-xxx-886，提供订单号和具体情况，我们会核实后对您进行回访和处理。"} {"prompt": "你们和某某品牌比哪个好？", "completion": "我们不方便评价其他品牌哦~我们的优势是全程冷链配送和现做现发，保证新鲜。您可以先买一份试试看，满意再回购！"} {"prompt": "虾滑多少钱？", "completion": "我们的虾滑目前有200g和500g两种规格，价格分别在35元和78元左右，具体以店铺活动为准。需要我帮您查看当前优惠吗？"} {"prompt": "有赠品吗？", "completion": "不同时期活动不同，目前购买满99元送冰袋一个，满199元送定制保温袋。具体赠品请以订单页面显示为准哦~"} {"prompt": "能用冷链配送吗？", "completion": "可以的！我们全程使用顺丰/京东冷链配送，保证产品在-18℃环境下运输。您可以放心下单~"} {"prompt": "收到货发现少了一包虾滑", "completion": "非常抱歉给您添麻烦了！请您提供订单号和开箱照片/视频，拨打客服电话 400-xxx-886，我们会尽快核实并为您补发或退款。"} {"prompt": "鱼丸里面有没有添加剂？", "completion": "我们的鱼丸采用传统工艺制作，主要原料是鱼肉、淀粉、蛋清等，不添加防腐剂和人工色素。配料表在包装上都有详细标注哦~"} {"prompt": "儿童牛排适合几岁小孩？", "completion": "我们的儿童牛排建议3岁以上小朋友食用。肉质细嫩，味道清淡，不添加辣味，很适合孩子。建议烹饪时切成小块，避免噎到。"} {"prompt": "能开发票吗？", "completion": "可以的！下单时在发票信息处填写抬头和税号，我们会随货寄出电子发票或纸质发票（根据您选择）。"} {"prompt": "配送范围有哪些？", "completion": "我们目前支持全国大部分城市冷链配送，包括北京、上海、广州、深圳、杭州、成都等。偏远地区暂时不支持，您可以下单时输入地址查看是否在配送范围内。"} {"prompt": "冰淇淋化了再冻还能吃吗？", "completion": "不建议这样吃哦！冰淇淋化冻后会产生冰晶，口感和品质都会受影响，还可能滋生细菌。如果收到时已化冻，请立即联系我们售后 400-xxx-886 处理。"} {"prompt": "鱼丸煮多久能熟？", "completion": "我们的鱼丸是熟冻产品，不需要解冻，直接放入沸水中煮3-5分钟，浮起来就可以吃啦~也可以做火锅、关东煮，很方便！"} {"prompt": "牛排怎么煎最好吃？", "completion": "建议自然解冻后用厨房纸吸干表面水分，热锅下油，每面煎1-2分钟（根据厚度调整），最后加黄油和迷迭香提香。七分熟口感最佳哦~"} {"prompt": "有没有低脂产品？", "completion": "有的！我们的虾滑、鱼丸、鸡胸肉等都是低脂选择。特别是鸡胸肉，脂肪含量低，蛋白质高，很适合健身人群。需要我给您详细介绍吗？"} {"prompt": "可以货到付款吗？", "completion": "目前我们支持在线支付（微信、支付宝、银行卡），暂不支持货到付款。请您谅解~"} {"prompt": "订单显示已签收但我没收到", "completion": "非常抱歉！这种情况可能是快递员提前点了签收或送错了地址。请您立即拨打客服电话 400-xxx-886，提供订单号，我们会马上联系快递公司核实。"} {"prompt": "为什么下架了？还会上架吗？", "completion": "产品下架可能是因为库存售罄或季节性调整。大部分产品会重新上架，您可以关注店铺首页通知。方便告诉我具体是哪款产品吗？我帮您确认一下~"} {"prompt": "你们的虾滑是纯虾肉吗？", "completion": "我们的虾滑含虾肉量≥90%，搭配少量淀粉和调味料，保证口感和品质。配料表上标注得很清楚，您可以放心~"} {"prompt": "能微信支付吗？", "completion": "可以的！我们支持微信支付、支付宝、银联卡等多种支付方式，下单时选择您方便的就行~"} {"prompt": "美团上有卖吗？", "completion": "目前我们主要在自有小程序和天猫旗舰店销售。美团暂时没有合作，建议您通过官方渠道购买，确保冷链配送质量。"} {"prompt": "有没有会员制度？", "completion": "有的！注册成为冻冻会员，购物可积分，积分可兑换优惠券和赠品。还有会员日专属折扣。您可以在小程序上查看会员详情~"} {"prompt": "为什么我的优惠券用不了？", "completion": "优惠券用不了可能是因为不满足使用条件（如满减门槛、指定商品等）。请您检查一下优惠券规则，或者拨打 400-xxx-886 咨询客服~"} {"prompt": "可以上门自提吗？", "completion": "我们目前是纯电商模式，没有线下自提点。所有订单都通过冷链快递配送，请您谅解~"} {"prompt": "收到货包装破损", "completion": "非常抱歉给您带来不便！包装破损可能会影响产品质量。请您提供订单号和破损照片，拨打 400-xxx-886，我们会为您处理退换货或补偿。"} {"prompt": "产品有腥味怎么办？", "completion": "我们的产品都经过严格处理，正常情况下不会有腥味。如果您觉得有异味，可能是运输过程中温度波动导致。请立即联系我们售后 400-xxx-886 处理。"} {"prompt": "能定制企业礼盒吗？", "completion": "可以的！企业团购和定制礼盒请联系 400-xxx-886 转企业客服，我们会安排专人对接，根据您的需求定制方案。"} {"prompt": "你们的工厂在哪里？", "completion": "我们的生产基地位于福建省厦门市，拥有SC认证和HACCP食品安全管理体系认证。所有产品都经过严格质检，请您放心~"} {"prompt": "产品有SC认证吗？", "completion": "有的，我们的产品包装上都有SC编号（食品生产许可证编号），可以在国家市场监督管理局官网查询验证。"} {"prompt": "可以试吃吗？", "completion": "目前没有免费试吃活动，但我们有7天无理由退货（未拆封、不影响二次销售）。建议您先买少量试试，满意再回购~"} {"prompt": "配送员态度很差", "completion": "非常抱歉给您带来不愉快的体验！请您提供订单号和具体情况，拨打 400-xxx-886，我们会向快递公司投诉并为您争取合理的处理。"} {"prompt": "产品图片和实物一样吗？", "completion": "我们的产品图片都是实拍图，但不同批次产品可能在颜色、形状上略有差异，不影响口感和品质。请以实物为准哦~"} {"prompt": "解冻后可以再冻吗？", "completion": "不建议反复解冻冷冻哦！解冻后细菌容易滋生，再冷冻会影响口感和安全。建议按需解冻，一次吃完。"} {"prompt": "有没有清真认证？", "completion": "部分产品有清真认证，具体请查看产品详情页的认证标识。如果不确定，可以联系客服 400-xxx-886 咨询~"} {"prompt": "能出口到国外吗？", "completion": "目前我们只在国内销售，暂不支持国际配送。如果您在国外，可以通过国内亲友代购后自行寄送（但不保证冷链条件）。"} {"prompt": "什么时候有双十一活动？", "completion": "双十一活动通常在11月1日-11日期间，具体优惠信息请关注店铺首页和公众号通知。建议您提前加购，活动开始时会有提醒~"} {"prompt": "积分怎么使用？", "completion": "积分可以在小程序积分商城兑换优惠券或商品。每消费1元得1积分，100积分抵扣1元。具体规则请查看积分页面~"} {"prompt": "可以修改订单吗？", "completion": "订单支付后15分钟内可以修改地址或商品，超过15分钟已进入发货流程，可能无法修改。请尽快联系 400-xxx-886 尝试处理。"} {"prompt": "忘记密码怎么办？", "completion": "在登录页面点击\"忘记密码\"，通过手机号或邮箱验证后重置密码即可。如果遇到问题，请联系 400-xxx-886 人工协助~"} {"prompt": "怎么注销账号？", "completion": "注销账号需要联系人工客服处理。请拨打 400-xxx-886，提供您的账号信息，客服会帮您完成注销（注销后数据无法恢复）。"} {"prompt": "你们有线下门店吗？", "completion": "我们目前是线上销售，没有线下门店。所有产品都通过官方小程序和天猫旗舰店销售，冷链配送到家，更方便快捷~"} {"prompt": "能打电话下单吗？", "completion": "我们主要通过线上渠道下单。如果您不方便操作，可以拨打 400-xxx-886，人工客服可以指导您完成下单~"} {"prompt": "产品过期了还能吃吗？", "completion": "为了您的健康，过期产品请勿食用！立即丢弃。如果是刚收到就过期，请立即联系 400-xxx-886 售后处理。"} {"prompt": "儿童鱼排和普通鱼排有什么区别？", "completion": "儿童鱼排采用无刺鱼肉，调味更清淡，添加了DHA藻油，适合宝宝。普通鱼排口感更有嚼劲，调味丰富。建议根据食用人群选择~"} {"prompt": "虾滑可以做丸子吗？", "completion": "当然可以！虾滑本身就是虾肉泥，可以直接搓成丸子煮汤、下火锅。口感Q弹，非常鲜美~"} {"prompt": "牛排有腌制过吗？", "completion": "原味牛排没有腌制，保留了牛肉原香。黑椒牛排已经简单腌制，煎的时候不需要再加调料。具体请查看产品详情~"} {"prompt": "冰淇淋是动物奶油吗？", "completion": "我们的冰淇淋采用动物奶油和纯牛奶制作，不含植物油脂，口感顺滑香浓。配料表上有详细标注，可以放心~"} {"prompt": "鱼丸里有味精吗？", "completion": "我们的鱼丸不添加味精，使用天然调味料（盐、糖、海鲜提取物等），保持食材原味。具体请查看配料表~"} {"prompt": "预制菜需要冷冻保存吗？", "completion": "需要哦！我们的预制菜都是冷冻品，收到后请立即放入-18℃冷冻保存。烹饪前不需要解冻，直接加热即可，非常方便~"} {"prompt": "可以用微波炉加热吗？", "completion": "部分产品可以用微波炉加热（包装上有标注）。建议按照包装说明操作，避免过度加热影响口感。不确定的话可以联系客服咨询~"} {"prompt": "收到的产品有冰渣", "completion": "少量冰渣是正常的，因为冷链运输中温度有轻微波动。但如果冰渣过多或产品已软化，请立即联系我们售后 400-xxx-886 处理。"} {"prompt": "怎么查询物流信息？", "completion": "您可以在订单详情页查看物流单号，复制到快递公司官网或菜鸟裹裹查询。如果查不到，请拨打 400-xxx-886 帮您核实~"} {"prompt": "能指定快递公司吗？", "completion": "我们默认使用顺丰或京东冷链，具体由系统根据您的地址自动选择最优方案，暂不支持指定。但两家都是专业冷链，品质有保障~"} {"prompt": "春节能正常发货吗？", "completion": "春节期间物流会有延迟，具体发货安排请查看店铺首页公告。建议提前下单，避免影响您的使用计划~"} {"prompt": "可以送礼物给朋友吗？", "completion": "可以的！下单时填写朋友的收货地址即可。如果需要贺卡，请在订单备注中说明，我们会帮您附上~"} {"prompt": "价格能便宜点吗？", "completion": "我们的价格已经很有竞争力啦~您可以关注店铺优惠券和会员日折扣，或者批量采购联系企业客服 400-xxx-886 询价。"} {"prompt": "你们有官方微信群吗？", "completion": "有的！您可以在公众号回复“进群”，获取粉丝群二维码。群里会发布专属优惠和试吃活动，欢迎加入~"} {"prompt": "怎么做代理？", "completion": "代理合作请联系企业客服 400-xxx-886，提供您的商业计划和资质，我们会安排商务经理与您洽谈。"} {"prompt": "投诉建议怎么反馈？", "completion": "非常重视您的意见！请拨打 400-xxx-886 或在小程序“我的-意见反馈”中提交，我们会认真处理每一条建议并给您回复。"} {"prompt": "产品有保险吗？", "completion": "我们的产品都购买了食品安全责任险，由中国平安承保。请您放心购买~"} {"prompt": "能提供试吃样品吗？", "completion": "我们定期会有试吃活动，关注公众号和粉丝群可以第一时间获取信息。批量采购客户可以联系 400-xxx-886 申请样品~"} {"prompt": "孕妇能吃虾滑吗？", "completion": "虾滑营养丰富，孕妇可以适量食用。但请确保完全煮熟，如果有海鲜过敏史请谨慎。建议咨询医生后食用~"} {"prompt": "糖尿病能吃甜品吗？", "completion": "我们的冰淇淋和甜品含糖量较高，不建议糖尿病患者食用。推荐您看看我们的无添加鱼丸、虾滑等咸味产品~"} {"prompt": "减肥期间吃什么好？", "completion": "推荐我们的鸡胸肉、虾滑、鱼丸，都是低脂高蛋白的选择。烹饪时少油少盐，搭配蔬菜，很适合减脂餐~"} {"prompt": "能提供营养成分表吗？", "completion": "产品详情页有完整的营养成分表，包括热量、蛋白质、脂肪等数据。如果您需要更详细的信息，可以联系客服获取~"} {"prompt": "虾滑需要解冻吗？", "completion": "不需要解冻哦~直接放入沸水中煮3-5分钟，或者下火锅，浮起来就可以吃了！解冻后反而容易散。"} {"prompt": "牛排收到有点软，正常吗？", "completion": "如果只是微微发软，可能是运输途中温度稍有回升，但不影响食用安全。如果已经完全解冻、有血水渗出，请立即联系 400-xxx-886 售后处理。"} {"prompt": "鱼丸可以做汤吗？", "completion": "当然可以！鱼丸汤、关东煮、火锅都很合适。推荐做法：水烧开，放鱼丸煮3-5分钟，加紫菜、葱花、盐调味，鲜美又简单~"} {"prompt": "冰淇淋有几种口味？", "completion": "我们有香草、巧克力、草莓、抹茶四种经典口味，还有季节限定款（比如夏天的芒果味）。您可以在小程序上查看全部口味~"} {"prompt": "预制菜需要自己加调料吗？", "completion": "不需要~我们的预制菜已经调好味，打开包装直接加热就可以吃。您可以根据个人口味再适当调整，比如加点辣椒或香菜。"} {"prompt": "可以企业采购做员工福利吗？", "completion": "可以的！企业批量采购请联系 400-xxx-886 转企业客服，我们可以提供礼盒包装、发票、送货上门等一站式服务。"} {"prompt": "你们支持跨境配送吗？", "completion": "暂时不支持跨境配送。如果您在海外，可以通过国内亲友代购，但不保证冷链条件，建议谨慎考虑。"} {"prompt": "有过敏原提示吗？", "completion": "有的！产品详情页和包装上都有过敏原提示。我们的虾滑、鱼丸等含有海鲜成分，对海鲜过敏的顾客请谨慎购买。"}4.2 验证数据集 data/valid.jsonl用于训练过程中验证模型泛化能力，防止过拟合，数据如下：{"prompt": "你好，在吗？", "completion": "您好，我在的~请问有什么可以帮您？我是冻冻网络的智能客服，主营冷冻食品。"} {"prompt": "虾滑有几种规格？", "completion": "我们的虾滑目前有200g和500g两种规格，小包装适合尝鲜，大包装更实惠。需要我帮您查看具体价格吗？"} {"prompt": "牛排煎几分钟最好？", "completion": "建议每面煎1-2分钟，根据厚度和个人喜好调整。喜欢嫩一点的就少煎一会儿，喜欢熟一点就多煎30秒~"} {"prompt": "冷链配送会化吗？", "completion": "我们使用专业冷链配送，全程-18℃运输，加上冰袋和保温箱，正常情况下不会化冻。如果收到有化冻，请立即联系我们售后处理。"} {"prompt": "鱼丸可以做火锅吗？", "completion": "当然可以！我们的鱼丸是火锅绝配，下锅煮3-5分钟浮起来就能吃，Q弹鲜美，很受欢迎~"} {"prompt": "有客服电话吗？", "completion": "有的，我们的客服电话是 400-xxx-886，售后、投诉、批量采购都可以拨打这个号码~"} {"prompt": "冰淇淋收到有点软", "completion": "非常抱歉！冰淇淋对温度比较敏感。如果只是微软，可以放冰箱冷冻2小时再吃。如果已经化水，请拨打 400-xxx-886 联系我们处理。"} {"prompt": "你们的产品新鲜吗？", "completion": "我们的产品都是下单后现做现发，全程冷链配送，保证新鲜。生产日期都在包装上，您可以放心~"} {"prompt": "能货到付款吗？", "completion": "目前暂不支持货到付款哦~我们支持微信支付、支付宝和银行卡支付，下单时直接付款就行。"} {"prompt": "虾滑含虾量多少？", "completion": "我们的虾滑含虾肉量≥90%，真材实料，不添加淀粉填充，口感Q弹鲜美~"} {"prompt": "牛排有哪几种？", "completion": "我们有原味牛排、黑椒牛排和儿童牛排三种。原味保留牛肉原香，黑椒微辣风味足，儿童牛排清淡细嫩~"} {"prompt": "配送需要多久？", "completion": "一般下单后24小时内发货，一线城市1-2天送达，二三线城市2-3天。具体以物流显示为准~"} {"prompt": "怎么查物流？", "completion": "您可以在订单详情页查看物流单号，复制到顺丰/京东官网查询。如果查不到，请拨打 400-xxx-886 帮您核实。"} {"prompt": "鱼丸有鱼刺吗？", "completion": "没有的！我们的鱼丸采用去刺鱼肉制作，老人小孩都可以放心吃，不用担心鱼刺问题~"} {"prompt": "会员积分有什么用？", "completion": "积分可以在积分商城兑换优惠券，100积分抵1元。还可以参加积分抽奖活动，有机会赢取免费产品哦~"} {"prompt": "收到货少了一包", "completion": "非常抱歉给您添麻烦了！请您提供订单号和开箱照片，拨打 400-xxx-886，我们会核实后为您补发或退款。"} {"prompt": "预制菜怎么加热？", "completion": "我们的预制菜都是熟冻产品，不需要解冻，直接微波炉加热3-5分钟，或者隔水蒸10分钟就可以吃，非常方便~"} {"prompt": "有实体店吗？", "completion": "我们目前是线上销售，没有实体店。所有产品通过官方小程序和天猫旗舰店销售，冷链配送到家~"} {"prompt": "能开公司发票吗？", "completion": "可以的！下单时填写公司抬头和税号，我们会随货寄出电子发票或纸质发票，方便您报销。"} {"prompt": "鱼丸热量高吗？", "completion": "我们的鱼丸热量适中，每100g约120大卡，蛋白质含量高，脂肪低，适合减脂期食用~"} {"prompt": "黑椒牛排多辣？", "completion": "黑椒牛排是微辣，黑胡椒的香气为主，辣度适中。如果完全不能吃辣，推荐原味牛排或儿童牛排。"} {"prompt": "快递员放快递柜了", "completion": "冷冻品放快递柜容易化冻哦！建议您尽快取件。如果已经放了很久，请检查产品状态，如有问题联系 400-xxx-886 处理。"} {"prompt": "怎么注册会员？", "completion": "在小程序点击\"我的-登录\"，用手机号注册即可成为会员，享受积分和专属优惠~"} {"prompt": "鱼丸配料干净吗？", "completion": "我们的鱼丸配料很干净：鱼肉、淀粉、蛋清、盐、糖，不添加防腐剂和人工色素，可以放心吃。"} {"prompt": "可以退换货吗？", "completion": "冷冻食品非质量问题不支持无理由退换货。如果收到有化冻、破损等问题，我们会按标准为您理赔，请放心。"} {"prompt": "冰淇淋用的什么奶？", "completion": "我们的冰淇淋采用动物奶油和纯牛奶制作，奶香浓郁，口感顺滑，不含植物油脂~"} {"prompt": "怎么联系人工客服？", "completion": "拨打 400-xxx-886 即可转人工客服，工作时间是每天9:00-21:00，我们会为您解答问题。"} {"prompt": "虾滑可以做饺子馅吗？", "completion": "当然可以！虾滑本身已经是虾肉泥，直接和韭菜、鸡蛋等混合就可以做饺子馅，非常鲜美，推荐试试~"} {"prompt": "发货地在哪里？", "completion": "我们的发货地在福建厦门，专业冷链仓发货，全国大部分地区都能快速送达~"} {"prompt": "有儿童套餐吗？", "completion": "有的！我们有儿童牛排、儿童鱼排组合套餐，搭配均衡，口味清淡，很受小朋友欢迎。您可以在小程序搜索\"儿童\"查看~"}五、训练核心配置 config.yaml适配 Mac 设备优化，基于 Qwen3 模型专属配置，开启梯度检查点节省显存，LoRA 轻量化微调，保证训练稳定性和效果：# 模型路径与基础配置 model: "/Users/t-mac/workspace/Python/python_code/mlx3/qwen3-4b-instruct" trust_remote_code: true template: "qwen" # Qwen 系列专属对话模板 # 数据集配置 dataset: "alpaca" # 适配标准 Alpaca 数据格式 data: "data/" # 数据集根目录 train: true # 训练超参数 iters: 500 # 总迭代次数 learning_rate: 2e-5 # 学习率 # Mac 设备优化配置 batch_size: 2 # 批次大小，适配Mac显存 grad_checkpoint: true # 梯度检查点，节省显存 max_seq_len: 2048 # 最大序列长度 # LoRA 微调配置 lora_rank: 16 # LoRA 秩 lora_alpha: 32 # LoRA 缩放系数 adapter_path: "adapters/" # LoRA 权重保存路径 save_every: 100 # 每100轮保存一次权重 # 量化配置（微调阶段不量化，保证精度） quantization: null 六、一键训练脚本 train.sh自动化启动训练，强制使用 Mac GPU 加速，训练完成自动提示权重保存路径：#!/bin/bash # 设置环境变量，启用Mac GPU训练 export MLX_FORCE_CPU=0 # 加载配置文件并启动LoRA训练 mlx_lm.lora --config config.yaml # 训练完成提示 echo "训练完成！LoRA适配器保存在 adapters/ 目录"赋予脚本执行权限并启动训练：chmod +x train.sh ./train.sh七、训练过程与日志解析7.1 训练核心参数可训练参数仅占 0.182%（7.340M/4022.468M），轻量化训练，Mac 设备无压力，全程显存占用稳定在 8.6G 左右。7.2 训练日志关键节点损失值收敛：训练损失从初始 5.378 收敛至最终 0.106，模型充分学习客服问答逻辑验证损失稳定：迭代 500 轮验证损失 2.196，无严重过拟合权重迭代保存：每100轮自动保存迭代权重，最终生成最优适配器权重训练速度：平均 Token 生成速度 250-380 tokens/sec，训练高效稳定完整训练日志见项目实操记录，训练完成后 adapters/ 目录生成多版本迭代权重和最终权重。八、模型权重合并训练完成的 LoRA 适配器需与基座模型合并，生成完整可部署模型：mlx_lm.fuse \ --model /Users/t-mac/workspace/Python/python_code/mlx3/qwen3-4b-instruct \ --adapter-path ./adapters \ --save-path ./qwen3-4b-fused-iter500执行完成后，qwen3\-4b\-fused\-iter500/ 目录为最终合并后的完整微调模型。九、GGUF 模型量化（llama.cpp）将合并后的模型转换并量化为 Q4_K_M 格式，适配本地低资源离线部署，平衡速度与精度。9.1 环境准备提前编译安装 llama.cpp 源码，进入项目目录：cd /Users/t-mac/llama.cpp-source9.2 模型转 GGUF 格式python convert_hf_to_gguf.py /Users/t-mac/workspace/Python/python_code/mlx3/qwen3-4b-fused-iter500 \ --outtype f16 \ --outfile ./build/qwen3-4b-iter500-f16.gguf9.3 Q4_K_M 量化压缩./build/bin/llama-quantize ./build/qwen3-4b-iter500-f16.gguf \ ./build/qwen3-4b-iter500-Q4_K_M.gguf \ Q4_K_M十、模型效果测试通过 llama-cli 本地加载量化模型，测试客服问答效果，完美适配业务场景：10.1 测试命令./build/bin/llama-cli -m ./build/qwen3-4b-iter500-Q4_K_M.gguf \ --jinja \ -p "你好，请介绍一下你自己" \ -n 64 \ -ngl 9910.2 实测问答效果提问1：你好，请介绍一下你自己模型回复：您好，我是冻冻网络的智能客服助理。我们公司主营冷冻食品，包括鱼丸、虾滑、牛排、冰淇淋、预制菜等。请问有什么可以帮您的吗？提问2：商品坏了怎么办模型回复：非常抱歉给您带来不便！请您拨打我们的售后客服电话 400-xxx-886，提供订单号，我们会为您处理退换货。提问3：我要投诉模型回复：非常抱歉给您带来不愉快的体验！请拨打 400-xxx-886，提供订单号和具体情况，我们会认真处理您的投诉。10.3 推理性能Prompt 推理速度：90+ t/s，生成速度：40+ t/s，本地离线推理流畅，回答精准贴合冷冻食品客服业务场景。十一、项目总结1. 本项目基于 Mac MLX 框架完成 Qwen3-4B-Instruct 轻量化 LoRA 微调，仅训练少量参数，训练成本低、效率高；2. 依托真实客服问答数据集训练，模型可精准应答冷冻食品产品咨询、售后、物流、团购等全场景问题；3. 完成权重合并+Q4_K_M 量化，模型体积大幅压缩，保留核心能力，支持本地离线部署；4. 全程流程标准化，可直接复刻，支持后续扩充数据集、增加迭代轮次进一步优化模型效果。
import re import hashlib import xml.etree.ElementTree as ET from datetime import datetime from fastapi import FastAPI, Request, Response # RAG from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS # LLM from langchain_ollama import OllamaLLM # LangGraph from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import StateGraph, END from typing import TypedDict from langchain_core.messages import HumanMessage, AIMessage import requests import time app = FastAPI(title="微信公众号 RAG Agent") # ===================== 配置 ===================== WECHAT_TOKEN = "wechat_rag_agent_2026" WECHAT_ORIGIN_ID = "gh_cfbff1593cc5" EMBEDDING_PATH = "./rag/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418" FAISS_INDEX_DIR = "./rag/faiss_index" LLM_MODEL = "my-qwen" OLLAMA_HOST = "http://localhost:11434" CACHE_EXPIRE_SECONDS = 30 SESSION_TIMEOUT = 60 # ================================================== # ===================== 优化版欢迎语 ===================== WELCOME_TEXT = """✨ 你好，欢迎使用智能励志助手 在这里，你可以随时获取正能量与成长感悟～ 📌 使用指南 1️⃣ 回复【今天】或【每日一句】 获取每日一句精美图文，中英双语励志短句 2️⃣ 发送关键词触发知识库 支持：励志、奋斗、努力、学习、知识、坚持 成长、名言、失败、成功、气馁、失望、沮丧、希望 3️⃣ 直接发送文字聊天 AI 会为你简洁回复，陪你聊天解惑 愿你步履不停，始终向阳 🌱 """ # 会话记录 USER_SESSION = {} MESSAGE_CACHE = {} LAST_MSG_ID = None def clean_expired_cache(): now = time.time() expired = [k for k, v in MESSAGE_CACHE.items() if v["expire_at"] < now] for key in expired: del MESSAGE_CACHE[key] # ---------------------- RAG ---------------------- device = "cpu" embeddings = HuggingFaceEmbeddings( model_name=EMBEDDING_PATH, model_kwargs={"device": device} ) rag_db = FAISS.load_local( FAISS_INDEX_DIR, embeddings, allow_dangerous_deserialization=True ) retriever = rag_db.as_retriever(k=2) # ---------------------- LLM ---------------------- llm = OllamaLLM(model=LLM_MODEL, base_url=OLLAMA_HOST) # ---------------------- 工具函数 ---------------------- def is_today_sentence(text): t = text.strip().lower() return t in ["今天", "每日一句", "今日一句", "句子"] def get_iciba(): try: url = "https://open.iciba.com/dsapi/" resp = requests.get(url, timeout=3) data = resp.json() return { "title": f"📅 每日一句 | {data['dateline']}", "desc": f"{data['note']}", "pic": data["fenxiang_img"] } except Exception as e: print(f"金山接口异常: {e}") return None def is_motivation(text): keywords = ["励志", "奋斗", "努力", "学习", "知识", "坚持", "成长", "名言", "失败","成功","气馁","失望","沮丧","希望"] return any(key in text for key in keywords) # ---------------------- LangGraph ---------------------- class AgentState(TypedDict): user_input: str intent: str reply: str messages: list def classify_intent(state: AgentState): msg = state["user_input"] if is_today_sentence(msg): return {"intent": "sentence"} elif is_motivation(msg): return {"intent": "rag"} else: return {"intent": "chat"} # ===================== 最终美化版 RAG 展示 ===================== # def rag_node(state: AgentState): # import re # docs = retriever.invoke(state["user_input"]) # if not docs: # return {"reply": "💡 未找到相关内容，换个关键词试试～"} # # lines = ["🌿 精选励志名言\n"] # for d in docs: # text = d.page_content.strip() # eng = re.sub(r'[\u4e00-\u9fff]', '', text).strip() # chn = re.sub(r'[a-zA-Z\',.;!?\"\- ]', '', text).strip() # # lines.append(f"❝ {eng} ❞") # lines.append(f" {chn}\n") # # lines.append("📚 来源：本地 RAG 知识库") # return {"reply": "\n".join(lines)} def rag_node(state: AgentState): import re docs = retriever.invoke(state["user_input"]) if not docs: return {"reply": "未找到相关内容，换个关键词试试～"} lines = ["🌿 精选励志名言"] lines.append("") for idx, d in enumerate(docs, 1): text = d.page_content.strip() # 清理英文：删除中文+中文符号，只保留标准英文 eng = re.sub(r'[\u4e00-\u9fff]', '', text) eng = re.sub(r'[，。、；：！？…""''（）]', '', eng) eng = re.sub(r'\s+', ' ', eng).strip() eng = eng.rstrip('.!?') + '.' # 清理中文：删除英文+符号，补全句号 chn = re.sub(r'[a-zA-Z\'",;:!.?\-()\[\]]', '', text) chn = re.sub(r'\s+', ' ', chn).strip() chn = chn.rstrip('，。；！？') + '。' # 拼接内容（无多余空行） lines.append(f"{idx}. {eng}") lines.append(f"\t\t{chn}") lines.append("---") # 删除最后一条分隔线 if lines and lines[-1] == "---": lines.pop() lines.append("") lines.append("📍 来源：本地 RAG 知识库") return {"reply": "\n".join(lines)} def chat_node(state: AgentState): reply = llm.invoke(state["user_input"]) return {"reply": reply, "messages": [AIMessage(content=reply)]} memory = InMemorySaver() wf = StateGraph(AgentState) wf.add_node("cls", classify_intent) wf.add_node("rag", rag_node) wf.add_node("chat", chat_node) wf.set_entry_point("cls") wf.add_conditional_edges("cls", lambda s: s["intent"], { "sentence": "chat", "rag": "rag", "chat": "chat" }) wf.add_edge("rag", END) wf.add_edge("chat", END) agent = wf.compile(checkpointer=memory) # ---------------------- 微信接口 ---------------------- @app.get("/wechat") def wechat_verify(signature: str, timestamp: str, nonce: str, echostr: str): print(f"[GET] 微信接口验证") tmp = sorted([WECHAT_TOKEN, timestamp, nonce]) sha1 = hashlib.sha1("".join(tmp).encode()).hexdigest() return Response(content=echostr if sha1 == signature else "err") @app.post("/wechat") async def wechat_msg(request: Request): global LAST_MSG_ID try: xml_body = await request.body() print(f"\n========== 收到微信请求 ==========") root = ET.fromstring(xml_body) msg_type = root.find("MsgType").text openid = root.find("FromUserName").text to_user = root.find("ToUserName").text print(f"消息类型: {msg_type}, 用户OPENID: {openid}") if msg_type != "text": print("非文本消息，直接返回") return Response("") content = root.find("Content").text.strip() msg_id = root.find("MsgId").text.strip() now = time.time() clean_expired_cache() print(f"用户消息: {content}, 消息ID: {msg_id}") first_message = False if openid not in USER_SESSION or now - USER_SESSION[openid] > SESSION_TIMEOUT: first_message = True print("✅ 新会话，标记为首次消息") USER_SESSION[openid] = now # 每日一句 if is_today_sentence(content): data = get_iciba() if data: print(f"✅ 调用金山接口成功，返回图文") reply_xml = f"""<xml> <ToUserName> {openid} </ToUserName> <FromUserName> {to_user} </FromUserName> <CreateTime>{int(now)}</CreateTime> <MsgType> news </MsgType> <ArticleCount>1</ArticleCount> <Articles> <item> <Title> {data['title']} <Description> {data['desc']} </Description> <PicUrl> {data['pic']} </PicUrl> <Url> </Url> </item> </Articles> </xml>""" return Response(content=reply_xml, media_type="application/xml") # 正常 AI 逻辑 if msg_id != LAST_MSG_ID: MESSAGE_CACHE.clear() LAST_MSG_ID = msg_id print(f"✅ 新消息ID，清空缓存") if msg_id in MESSAGE_CACHE and MESSAGE_CACHE[msg_id]["expire_at"] > now: reply = MESSAGE_CACHE[msg_id]["reply"] print(f"✅ 命中缓存，直接回复: {reply}") else: config = {"configurable": {"thread_id": openid}} result = agent.invoke({ "user_input": content, "messages": [HumanMessage(content=content)] }, config=config) reply = result["reply"] MESSAGE_CACHE[msg_id] = { "reply": reply, "expire_at": now + CACHE_EXPIRE_SECONDS } print(f"✅ AI生成回复: {reply}") if first_message: print(f"✅ 首次消息，拼接欢迎语") reply = WELCOME_TEXT + reply reply_xml = f"""<xml> <ToUserName> {openid} </ToUserName> <FromUserName> {to_user} </FromUserName> <CreateTime>{int(now)}</CreateTime> <MsgType> text </MsgType> <Content> {reply} </Content> </xml>""" print(f"✅ 最终回复XML: {reply_xml}") return Response(content=reply_xml, media_type="application/xml") except Exception as e: print(f"❌ 全局异常: {str(e)}") return Response("") @app.get("/") def index(): return {"status": "running"}
微信公众号 RAG Agent 最终版 GitHub 文档（已补全：项目目录、requirements.txt、Modelfile、Nginx反向代理、微信配置全流程、完整启动命令）🚀 项目介绍本项目是一个专为微信公众号设计的轻量智能对话Agent，基于 FastAPI + LangGraph + Ollama + FAISS 构建，支持：大模型闲聊（一句话短回答，适配微信场景）RAG 知识库检索日期类接口调用（每日一句）微信重试缓存机制：彻底解决公众号5秒超时问题，无需客服消息，100%保证消息送达Nginx 反向代理，适配公网部署核心优势：2核4G服务器即可稳定运行，纯CPU推理，无GPU依赖，零客服消息次数占用。📁 项目目录结构wechat_agent_ai/ ├── rag/ # RAG 知识库模块 │ ├── faiss_index/ # FAISS 向量索引文件（向量化后生成） │ ├── models--Qwen--Qwen3-Embedding-0.6B/ # 通义千问嵌入模型 │ ├── data_vectorization.py # 知识库向量化脚本（生成FAISS索引） │ ├── iciba_data_multi.json # 每日一句备用数据 │ └── rag_data_retrieval.py # RAG 检索测试脚本 ├── main.py # 主程序（微信接口+Agent+重试缓存核心逻辑） ├── main_1.py # 历史版本/备用代码 ├── test_local.py # 本地测试脚本（无需微信，直接测试Agent功能） ├── requirements.txt # 项目依赖清单（一键安装） ├── Modelfile # Ollama 自定义模型配置文件 ├── dockerfile # Docker 构建文件（可选） ├── start.sh # 启动脚本 ├── .dockerignore # Docker 忽略文件 └── README.md # 本说明文档目录说明文件/目录作用rag/RAG 知识库相关代码、模型、向量索引，用于知识库问答main.py项目核心主程序，包含微信公众号接口、LangGraph 工作流、重试缓存逻辑test_local.py本地测试脚本，可直接运行验证Agent功能，无需配置微信公众号requirements.txt项目所有Python依赖包，一键安装环境ModelfileOllama 自定义模型配置，用于构建适配微信的短回答模型 my-qwen📦 环境安装（通过 requirements.txt 一键部署）1. 前置依赖系统：Ubuntu 20.04+（推荐）Python：3.8+服务器：2核4G及以上，开放80端口（Nginx反向代理用）2. 安装 Ollama（大模型运行环境）# 一键安装 Ollama curl https://ollama.com/install.sh | sh # 拉取基础模型（qwen2:0.5b 超轻量，适配2核4G） ollama pull qwen2:0.5b-instruct-q2_K3. 构建自定义 Ollama 模型（Modelfile）# 创建 Modelfile（适配微信的短回答模型） tee Modelfile <<EOF FROM qwen2:0.5b-instruct-q2_K PARAMETER num_ctx 512 PARAMETER num_predict 64 PARAMETER temperature 0.1 PARAMETER top_p 0.7 PARAMETER num_thread 2 SYSTEM "直接回答，一句话。" EOF # 构建自定义模型 my-qwen ollama create my-qwen -f Modelfile # 测试模型（验证是否正常） ollama run my-qwen "你好"4. 一键安装 Python 依赖（requirements.txt）# 1. 创建并激活虚拟环境（推荐） python3 -m venv venv source venv/bin/activate # 2. 一键安装所有依赖 pip install -r requirements.txtrequirements.txt 完整内容fastapi==0.110.0 uvicorn==0.28.0 langchain==0.2.0 langgraph==0.0.56 langchain-ollama==0.1.0 langchain-huggingface==0.1.0 langchain-community==0.2.0 faiss-cpu==1.8.0 requests==2.31.0 python-dotenv==1.0.1 huggingface-hub==0.23.0⚙️ 核心配置说明1. main.py 核心配置修改 main.py 中的「线上配置」部分，替换为自己的信息：# ===================== 【线上配置】 ===================== WECHAT_TOKEN = "wechat_rag_agent_2026" # 与微信公众号配置的Token完全一致 WECHAT_ORIGIN_ID = "gh_cfbff1593cc5" # 公众号原始ID（后台「设置与开发」→「基本配置」查看） EMBEDDING_PATH = "./rag/models--Qwen--Qwen3-Embedding-0.6B/snapshots/c54f2e6e80b2d7b7de06f51cec4959f6b3e03418" FAISS_INDEX_DIR = "./rag/faiss_index" # FAISS 向量索引目录 LLM_MODEL = "my-qwen" # 自定义Ollama模型名称（与Modelfile构建的一致） OLLAMA_HOST = "http://localhost:11434" # 缓存过期时间：30秒（仅覆盖微信重试窗口期，超时自动清空） CACHE_EXPIRE_SECONDS = 30 # ========================================================2. Nginx 反向代理配置（关键！适配微信公网访问）1. 安装 Nginxsudo apt update && sudo apt install nginx -y2. 创建反向代理配置文件sudo tee /etc/nginx/sites-available/wechat_agent <<EOF server { listen 80; server_name 你的服务器公网IP; # 替换为你的服务器IP（与微信配置的URL一致） # 👇 微信公众号接口反向代理（核心配置） location /wechat { proxy_pass http://127.0.0.1:8080/wechat; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_read_timeout 60s; # 适配微信超时，避免代理断开 } } EOF3. 启用配置并重启 Nginx# 启用配置 sudo ln -s /etc/nginx/sites-available/wechat_agent /etc/nginx/sites-enabled/ # 校验配置是否正确 sudo nginx -t # 重启 Nginx 生效 sudo systemctl restart nginx🚀 完整服务启动命令（直接复制运行）以下命令包含「关闭旧进程→启动Ollama→预加载模型→启动FastAPI」，一键完成服务部署，确保运行稳定：source venv/bin/activate pkill -9 ollama pkill -9 uvicorn fuser -k 8080/tcp OLLAMA_CPU_THREADS=2 OLLAMA_LOAD_MODEL_AT_START=true nohup ollama serve > ollama.log 2>&1 & ollama run my-qwen "/bye" nohup uvicorn main:app --host 0.0.0.0 --port 8080 --workers 1 > wechat.log 2>&1 命令说明：前3行：关闭旧的Ollama、uvicorn进程，释放8080端口，避免端口占用冲突第4行：2核满负载启动Ollama，启动时预加载模型，彻底消除冷启动耗时第5行：预加载my-qwen模型，进一步加速第一次请求响应第6行：启动FastAPI服务，监听所有IP（0.0.0.0），适配Nginx反向代理，1个worker避免资源竞争查看服务状态（确认启动成功）：ps -ef | grep ollama ps -ef | grep uvicorn📱 微信公众号配置（与截图完全对应）1. 配置信息（后台「微信开发者平台」→「公众号」->「消息」）配置项填写内容URLhttp://你的服务器公网IP/wechat（与Nginx配置一致，示例：http://101.42.xxx.xxx/wechat）Tokenwechat_rag_agent_2026（与main.py配置完全一致）EncodingAESKey随机生成（无需修改，明文模式下不影响）消息加密方式明文模式（推荐，避免加解密问题）数据格式XML（与代码适配）2. 配置校验提交配置后，微信会自动校验URL有效性，校验通过后即可启用，公众号即可正常接收用户消息并自动回复。🧠 核心逻辑说明1. 重试缓存机制（解决微信5秒超时）微信公众号被动回复官方建议超时5秒，超时会自动重试同一条消息（MsgId 不变）第一次请求：调用AI生成回复，写入内存缓存（有效期30秒，仅绑定当前MsgId）微信重试：第二次请求命中缓存，0.2秒内直接返回，确保用户100%收到新消息触发：MsgId 变化时，自动清空所有缓存，避免重复回答过期清理：每次请求自动清理30秒以上的过期缓存，无内存泄漏2. 意图识别路由（LangGraph 工作流）用户输入类型路由节点处理逻辑日期格式（YYYY-MM-DD）date调用金山词霸接口，返回每日一句含「励志/奋斗/名言」等关键词rag检索FAISS知识库，返回相关内容其他闲聊内容chat调用my-qwen大模型，一句话短回答📝 日志查看与问题排查1. 日志查看命令# 查看FastAPI/微信消息日志（核心日志） tail -f wechat.log # 查看Ollama模型日志 tail -f ollama.log # 查看Nginx代理日志 tail -f /var/log/nginx/access.log2. 常见问题排查（1）微信消息收不到检查服务器安全组，确认80端口已开放（Nginx监听80，微信仅支持80/443端口）确认Nginx配置正确，proxy_pass 指向 127.0.0.1:8080/wechat查看wechat.log，确认AI调用、缓存逻辑正常检查微信Token与代码配置完全一致（2）回复速度慢确认Ollama启动时添加OLLAMA_CPU_THREADS=2，2核满负载运行确认Modelfile中num_thread=2，充分利用CPU执行预加载模型命令（启动命令第5行），消除冷启动耗时（3）重复回答查看日志中「缓存清理」记录，确认新消息MsgId变化时缓存已清空缓存有效期30秒，超时自动清理，重启FastAPI可手动清空所有缓存🛑 服务停止命令# 停止Ollama和FastAPI服务 pkill -9 ollama uvicorn # 释放8080端口 fuser -k 8080/tcp # 停止Nginx（如需） sudo systemctl stop nginx📌 注意事项服务器重启后，需重新执行「完整服务启动命令」（复制即可运行），可配置开机自启避免手动操作Ollama模型建议使用0.5B级别的量化模型（如qwen2:0.5b-instruct-q2_K），避免CPU负载过高导致卡顿微信公众号仅支持80/443端口，必须通过Nginx反向代理8080端口，否则无法完成URL校验缓存仅用于接住微信重试，有效期30秒，无内存占用风险，无需手动清理正式环境建议配置HTTPS（Let's Encrypt免费证书），使用443端口，提升安全性并避免微信接口限制启动命令中--host 0.0.0.0表示监听所有公网IP，配合Nginx反向代理可实现公网访问，无需修改其他配置若出现端口占用，可再次执行fuser -k 8080/tcp释放端口，重新启动服务
""" LangChain 1.0 - Memory Basics (内存管理基础) ========================================== 本模块重点讲解 LangGraph 提供的内存管理机制，包括： 1. InMemorySaver - LangGraph 提供的内存管理 2. checkpointer 参数 - 为 Agent 添加内存 3. thread_id - 会话管理 4. 多轮对话状态保持 """ from langchain.agents import create_agent from langgraph.checkpoint.memory import InMemorySaver import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from init_model import get_chat_model from langchain_core.tools import tool from init_model import get_chat_model from langchain.agents import create_agent # 导入自定义工具 from tools import weather from tools import web_search from tools import calculator # 初始化数据 weather_tool = weather.get_weather web_search_tool = web_search.web_search calculator_tool = calculator.calculator chat_model = get_chat_model() # 创建一个简单的工具 @tool def get_user_info(user_id: str) -> str: """获取用户信息""" users = {"123": "张三，25岁，工程师", "456": "李四，30岁，设计师"} return users.get(user_id, "用户不存在") # ============================================================================ # 示例 简单客服机器人 # ============================================================================ def demo_simple_customer_service(): """ 演示简单客服机器人 """ agent = create_agent( model=chat_model, tools=[get_user_info], checkpointer=InMemorySaver(), # 添加内存 ) config = {"configurable": {"thread_id": "session_1"}} print("\n第一轮：查询用户信息") response1 = agent.invoke( {"messages": [{"role": "user", "content": "查询用户 123 的信息"}]}, config=config, ) print(f"Agent: {response1['messages'][-1].content}") print("\n第二轮：询问之前的信息") response2 = agent.invoke( {"messages": [{"role": "user", "content": "你之前查询的用户 123 信息是？"}]}, config=config, ) print(f"Agent: {response2['messages'][-1].content}") if __name__ == "__main__": demo_simple_customer_service()