1.从hagging face下载模型
2.把下载的模型文件,放到/usr/local/Qwen2-7B目录下
3.创建虚拟环境,安装依赖
1.环境安装
sudo yum update -y
sudo yum install -y python3 python3-pip git
2.创建虚拟环境并激活
python3 -m venv qwen2_env
source qwen2_env/bin/activate
conda activate qwen2_env
conda install numpy transformers torch langchain sentence-transformers jieba requests streamlit
3.将diet_ui.py文件放到根目录下
import streamlit as st
import requests
import re
import logging
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
from langchain.memory import ConversationBufferMemory
from langchain.chains import LLMChain
from langchain.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
from transformers import pipeline
from sentence_transformers import SentenceTransformer, models
import numpy as np
import jieba# 配置日志记录
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')# 加载 Qwen2 模型和分词器
try:tokenizer = AutoTokenizer.from_pretrained("/usr/local/Qwen2-7B", trust_remote_code=True)model = AutoModelForCausalLM.from_pretrained("/usr/local/Qwen2-7B", device_map="auto", trust_remote_code=True).eval()logging.info("分词器和模型加载成功")
except Exception as e:logging.error(f"加载分词器或模型时发生错误: {e}")raise# 公司业务接口地址
PORTFOLIO_API_URL = "https://xxxxx/plugin/portfolio"# 扩充关键词列表
KEYWORDS = ["方案", "配餐", "计划", "饮食安排", "食谱", "卡路里", "餐单", "膳食"]
# 允许的话题列表,添加配餐相关关键词
ALLOWED_TOPICS = ["健康", "营养", "饮食", "订单", "优惠券", "吃饭", "运动", "养生", "客服"] + KEYWORDS# 初始化会话记忆,最多存储 10 轮对话
memory = ConversationBufferMemory(k=10)# 创建 HuggingFacePipeline
pipe = pipeline("text-generation",model=model,tokenizer=tokenizer,max_new_tokens=500,do_sample=True,top_p=0.85,temperature=0.35
)
llm = HuggingFacePipeline(pipeline=pipe)# 创建一个简单的提示模板
prompt_template = PromptTemplate(input_variables=["input"],template="{input}"
)# 创建 LLMChain
chain = LLMChain(llm=llm, memory=memory, prompt=prompt_template)# 加载知识库
def load_knowledge_base(file_path):with open(file_path, 'r', encoding='utf-8') as file:lines = file.readlines()knowledge_base = []question = Noneanswer = ""for line in lines:line = line.strip()if line.startswith("Q:"):if question is not None:knowledge_base.append((question, answer))question = line[2:]answer = ""elif line.startswith("A:"):answer = line[2:]else:answer += line + " "if question is not None:knowledge_base.append((question, answer))return knowledge_base# 加载知识库
knowledge_base = load_knowledge_base("/root/knowledge_base.txt")# 手动加载模型组件
word_embedding_model = models.Transformer('/root/all-MiniLM-L6-v2')
pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())
encoder = SentenceTransformer(modules=[word_embedding_model, pooling_model])# 编码知识库中的问题
question_embeddings = [encoder.encode(question) for question, _ in knowledge_base]# 提取关键词
def extract_keywords(text):return list(jieba.cut(text))# 从知识库查找答案(结合关键词匹配和向量相似度)
def find_answer_from_knowledge_base(question, threshold=0.5):question_keywords = extract_keywords(question)question_embedding = encoder.encode(question)best_match_index = -1best_similarity = -1for i, (kb_question, _) in enumerate(knowledge_base):kb_keywords = extract_keywords(kb_question)# 检查关键词是否匹配if any(keyword in kb_keywords for keyword in question_keywords):similarity = np.dot(question_embedding, question_embeddings[i]) / (np.linalg.norm(question_embedding) * np.linalg.norm(question_embeddings[i]))if similarity > best_similarity and similarity >= threshold:best_similarity = similaritybest_match_index = iif best_match_index != -1:return knowledge_base[best_match_index][1]return None# 提取配餐参数
def extract_params(text):calory = Noneday = None# 优化后的卡路里提取正则表达式calory_pattern = re.compile(r'(?:每天|每日|每餐)?\s*(?:卡路里|热量|千卡|大卡)\s*(?:是|约|大概|为)?\s*(\d+)', re.IGNORECASE)# 优化后的天数提取正则表达式day_pattern = re.compile(r'(?:配|安排|制定)?\s*(\d+)\s*(?:天|日|天的餐|日的餐)', re.IGNORECASE)calory_match = calory_pattern.search(text)if calory_match:try:calory = int(calory_match.group(1))except ValueError:passday_match = day_pattern.search(text)if day_match:try:day = int(day_match.group(1))except ValueError:passreturn calory, day# 调用配餐接口
def get_meal_plan(calory, day):logging.debug(f"准备调用配餐接口,calory: {calory}, day: {day}")try:response = requests.get(PORTFOLIO_API_URL, params={"calory": calory, "day": day})logging.debug(f"接口响应状态码: {response.status_code}")logging.debug(f"接口响应内容: {response.text}")if response.status_code == 200:logging.debug("配餐接口调用成功")try:response_data = response.json()# 打印完整的响应数据,用于调试logging.debug(f"完整的接口响应数据: {response_data}")# 提取配餐方案信息sku_list = response_data.get("skuList", [])if sku_list:output_text = "以下是为您生成的配餐方案:\n"output_text += "=" * 50 + "\n"current_day = 0for sku in sku_list:sequential_days = sku.get("sequentialDays")if sequential_days is None:logging.warning("配餐数据中缺少 sequentialDays 字段")continueif sequential_days != current_day:current_day = sequential_daysoutput_text += f"\n第 {current_day} 天:\n"output_text += "-" * 50 + "\n"category_type = sku.get("categoryType", "未知餐别")total_kcal = sku.get("totalKcal", "未知热量")output_text += f" {category_type}({total_kcal} 千卡):\n"meal_list = sku.get("list", [])for meal in meal_list:meal_name = meal.get("name", "未知套餐")output_text += f" 套餐名称:{meal_name}\n"output_text += " 包含菜品:\n"category_list = meal.get("getCategoryList", [])for item in category_list:skuname = item.get("skuname", "未知菜品")price = item.get("price", "未知价格")energy = item.get("energy", "未知能量")output_text += f" - {skuname}(价格:{price} 元,能量:{energy} 千卡)\n"output_text += "=" * 50 + "\n"else:output_text = "获取配餐方案成功,但无具体信息"return output_textexcept ValueError:logging.error("无法将接口响应内容解析为 JSON 格式")return "调用接口成功,但无法解析响应内容"else:logging.error(f"配餐接口调用失败,状态码: {response.status_code}")return f"调用接口失败,状态码: {response.status_code}"except requests.RequestException as e:logging.error(f"调用配餐接口时发生网络错误: {e}")return f"调用接口时发生网络错误: {e}"# Streamlit UI
st.title("Nutribite 智能营养师聊天机器人")# 初始化聊天历史
if 'chat_history' not in st.session_state:st.session_state.chat_history = []# 初始化输入框值
if 'input_value' not in st.session_state:st.session_state.input_value = ""# 获取用户输入
user_input = st.text_input("请输入你的问题", value=st.session_state.input_value, key="input_key")if user_input:# 拼接历史对话和当前输入history_text = "\n".join([f"用户: {msg['user']}\n机器人: {msg['bot']}" for msg in st.session_state.chat_history]) + "\n" + user_inputlogging.debug(f"拼接后的文本: {history_text}")# 检查是否处于配餐流程中is_in_meal_process = "配餐" in history_text and ("请问,要配几天的餐?" in history_text or "请问每天的卡路里大概是多少?" in history_text)# 检查是否是允许的话题if not any(topic in history_text for topic in ALLOWED_TOPICS) and not is_in_meal_process:bot_response = "我是Nutribite智能營養師,請問我相關問題"else:# 从知识库查找答案answer = find_answer_from_knowledge_base(user_input)if answer:logging.debug("从知识库找到答案")bot_response = answerelse:# 检测用户问题是否与配餐相关,仅依据当前输入文本is_meal_related = any(keyword in user_input for keyword in KEYWORDS)# 尝试从拼接后的文本中提取 calory 和 daycalory, day = extract_params(history_text)if is_meal_related or is_in_meal_process:if day is None:if "请问,要配几天的餐?" in history_text:# 重新提取当前用户输入中的 day_, day = extract_params(user_input)if day is None:logging.debug("仍未提取到 day 参数,再次询问用户")bot_response = "抱歉,我没获取到配餐天数,请明确告知要配几天的餐。"else:if calory is None:logging.debug("提取到 day 参数,未提取到 calory 参数,询问用户")bot_response = "请问每天的卡路里大概是多少?"else:# 调用配餐接口bot_response = get_meal_plan(calory, day)else:logging.debug("未提取到 day 参数,询问用户")bot_response = "请问,要配几天的餐?"elif calory is None:logging.debug("未提取到 calory 参数,询问用户")bot_response = "请问每天的卡路里大概是多少?"else:# 调用配餐接口bot_response = get_meal_plan(calory, day)else:logging.debug("用户问题与配餐无关,使用 Qwen2 模型生成回复")bot_response = chain.run(user_input)# 更新聊天历史st.session_state.chat_history.append({"user": user_input, "bot": bot_response})# 清空输入框st.session_state.input_value = ""# 显示聊天历史
for msg in st.session_state.chat_history:st.markdown(f"**用户**:{msg['user']}")st.markdown(f"**机器人**:{msg['bot']}")
4.执行streamlit run diet_ui.py
这个智能体实现了知识库、调公司业务接口功能,具体效果图
然后访问公网xxxxx:8501