近年来,随着大语言模型(LLM, Large Language Model)在自然语言理解上的突破,传统推荐系统也迎来了一次全新的“语言驱动”升级。本文将以我开发的“AI 智能选车助手”为例,介绍 LLM 在智能决策系统中的角色,以及我们是如何将“自然语言需求”转化为“结构化推荐逻辑”的。
🎯 用户痛点:选车不想填表格,只想说人话
传统的购车推荐系统往往依赖用户去填写各种选项:
用途?
座位数?
动力类型?
预算区间?
这种方式虽然精确,但用户体验不够自然,也缺乏智能性。
然而现在,用户只需要说一句:
“我想买辆适合家庭出行的中型 SUV,预算二十万以内,续航最好超过 800 公里。”
我们就能把这句话转化为精准的推荐结果——这背后,靠的正是大语言模型。
🧠 模型能力:让 LLM 来理解你的购车需求
大语言模型的强大之处在于:
能理解非结构化语义;
能输出结构化数据;
能补全用户未明确提及的信息;
能学会领域专业术语(如车辆类型、动力形式等)。
🔢 实现方式:从语义结构到精准评分
我们为每辆车打分的方式如下:
属性维度 | 评分方式 |
---|---|
预算范围 | 区间匹配 + 容差惩罚 |
用途/类型 | 精准匹配打满分 |
动力匹配 | 完全匹配双倍得分,插混与油混兼容打1.5倍 |
能耗/电耗 | 按动力类型区分单位(L/100km vs kWh/100km) |
续航/座位数 | 模糊评分(越接近用户需求得分越高) |
评分函数样例:
def range_score(value, target, tolerance, weight):
delta = abs(value - target)
return weight * (1 - delta / tolerance) if delta <= tolerance else 0
最终的推荐结果是将得分排序,返回匹配度最高的 N 辆车。
💡 创新点:LLM + 评分引擎的组合价值
这个系统的亮点并不在于推荐算法本身,而在于LLM 所赋予的前端语义接口能力:
传统方法 | LLM 驱动的智能推荐 |
---|---|
用户需逐项填写参数 | 用户只需描述一句话 |
推荐逻辑死板 | 可调整权重灵活打分 |
无法理解模糊语言 | 可理解“长续航”、“适合家用”等词 |
不会补全默认值 | LLM 会自动填默认意图 |
这使得推荐系统从“选项列表”升级为“自然语言交互”系统,用户体验显著提升。
🏗️ 系统架构与技术栈
模块 | 技术 |
---|---|
前端界面 | Streamlit |
模型接口 | Moonshot / OpenAI GPT-4 LLM |
数据处理 | Pandas, JSON 数据库 |
可视化输出 | DataFrame + CSV 导出 |
能耗识别逻辑 | 动态单位判别(L vs kWh) |
编程语言 | Python 3.10+ |
🔄 核心功能实现
1. 自然语言解析模块(query_kimi)
接收用户输入的购车意图(自然语言)
调用 Moonshot/GPT 接口,输入自定义 System Prompt
模型输出标准化 JSON 结构,包含:
需求
字段:包含用途、预算、动力、续航等权重
字段:各指标重要性,用于个性化评分
示例输出:
{
"需求": {
"用途": "家庭出行",
"预算区间": {"min": 0, "max": 200000},
"动力类型": "油电混合",
"能耗上限": 7.0,
"续航需求_km": 800
},
"权重": {
"用途": 1,
"预算区间": 3,
"动力类型": 2,
"能耗上限": 2,
"续航需求_km": 2
}
}
✨ 亮点:即使用户没有明确提及某个参数(如驱动方式),模型也会补全合理的默认值。
2. 车辆数据库与结构化评分模块(score_car)
系统加载一份包含 1000 款汽车的 JSON 数据集,字段包括:
名称、价格区间、动力类型、用途、续航、电耗/油耗、驱动方式、座位数等
评分逻辑包含:
维度 | 匹配方式说明 |
---|---|
用途/类型 | 精准匹配得满分 |
动力类型 | 完全匹配加倍得分,油电与插混视为兼容加权 |
价格/预算 | 区间匹配 + 容差评分 |
能耗评分 | 根据动力类型判断单位:L/100km 或 kWh/100km |
续航评分 | 容差 ±200km 内逐步减分 |
函数示例:
def range_score(value, target, tolerance, weight):
delta = abs(value - target)
return weight * (1 - delta / tolerance) if delta <= tolerance else 0
3. 推荐函数(recommend_car)
批量对所有车辆打分
按得分降序排列
保留预算内且座位数符合的车型
返回前 N 个最优推荐
4. Streamlit 图形界面(your_script.py)
用户输入自然语言
可手动设置权重(8个评分维度)
实时展示推荐结果
支持 CSV 文件导出
自动识别电车/油车并切换能耗单位显示(kWh or L)
🌟 亮点:权重调整后即时影响推荐排序,用户控制力强。
📁 项目结构
project/
├── ai_car_selector_kimi.py # 主推荐逻辑 + LLM接口
├── your_script.py # Streamlit界面逻辑
├── vehicle_db_1000_named.json # 汽车数据库
📥 依赖安装
使用 pip 安装依赖:
pip install -r requirements.txt
requirements.txt 示例内容:
openai
streamlit
pandas
numpy
🚀 运行系统
Step 1:准备 API Key
你需要一个 Moonshot 或 OpenAI 的 API 密钥。如果你使用 Moonshot,可以在界面输入:
sk-xxxxxxxxxxxxxxxxxxxx
也可以设置为环境变量:
export MOONSHOT_API_KEY=sk-xxxxxxx
Step 2:启动界面(Streamlit)
streamlit run your_script.py
然后浏览器会自动打开界面,或访问:
http://localhost:8501
📌 使用方式
输入自然语言购车需求,如:
我想买一辆适合城市代步的纯电车,预算15万以内,续航不要低于400公里
选择展示推荐的数量(Top-N)
(可选)展开“高级设置”自定义评分权重(如:更注重动力类型、续航等)
点击“开始智能选车”按钮
查看推荐表格,并可导出 CSV 文件
📁 代码说明
ai_car_selector_kimi.py
import os # 用于访问环境变量
import json # 用于解析和生成 JSON 数据
import pandas as pd # 用于处理结构化数据
from openai import OpenAI # 导入 OpenAI 接口客户端
MOONSHOT_API_KEY = os.getenv("MOONSHOT_API_KEY", "sk-****************") # 从环境变量获取 API 密钥,或使用默认密钥,这里需要自己修改
client = OpenAI(api_key=MOONSHOT_API_KEY, base_url="https://api.moonshot.cn/v1") # 初始化 OpenAI 客户端,设置 base_url 为 moonshot 接口
MODEL_NAME = "moonshot-v1-8k" # 使用的模型名称
VEHICLE_DB = "vehicle_db_1000_named.json" # 本地车辆数据库文件名
SYSTEM_PROMPT = """ # 系统提示词,定义 AI 应该如何将用户意图结构化为标准 JSON
你是一名资深汽车顾问,根据用户的自然语言购车需求,严格生成符合以下格式和要求的 JSON 数据。
【必填字段】以下字段必须提供,若用户未明确指出,请使用指定默认值:
- 用途(可选项:家庭出行、城市代步、商务接待、长途出行、运动旅行、越野探险;默认:"家庭出行")
- 车辆类型(可选项:中型SUV、小型掀背、大型轿车、旅行车、房车、小型两厢、Coupe、MPV、硬派SUV、皮卡;默认:"中型SUV")
- 预算区间(格式:{"min": 数字, "max": 数字},默认:{"min": 0, "max": 200000})
- 座位数(整数,默认:5)
- 动力类型(可选项:油电混合、纯电、3.0T汽油、1.6L汽油、插电混动、2.5L汽油、氢燃料电池;默认:"油电混合")
- 驱动方式(可选项:两驱、前驱、后驱、四驱;默认:"两驱")
- 续航需求_km(单位:公里,默认:800)
- 能耗上限(数字,单位根据动力类型自动判定:燃油车为 L/100km,电动车为 kWh/100km;默认:7.0)
【说明】
- 无需让用户输入单位,系统将自动判断。
- “能耗上限”代表用户希望的最大单位能耗,不论是油耗还是电耗。
【硬性筛选规则】
1. 车辆价格超出预算直接排除。
2. 车辆座位数不足直接排除。
【输出格式】
仅输出以下 JSON 格式,不要添加任何多余内容:
{
"需求": {
"用途": "...",
"车辆类型": "...",
"预算区间": { "min": ..., "max": ... },
"座位数": ...,
"动力类型": "...",
"驱动方式": "...",
"续航需求_km": ...,
"能耗上限": ...
},
"权重": {
"用途": 数字,
"车辆类型": 数字,
"预算区间": 数字,
"座位数": 数字,
"动力类型": 数字,
"驱动方式": 数字,
"续航需求_km": 数字,
"能耗上限": 数字
}
}
请严格遵守字段名、格式和选项要求,确保字段齐全,不可遗漏。
"""
def query_kimi(user_text: str) -> dict: # 向 Moonshot 请求,将用户自然语言转换为结构化需求 JSON
completion = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_text}
],
temperature=0.3,
)
return json.loads(completion.choices[0].message.content) # 解析模型返回内容为 dict 格式
def normalize_weights(weights): # 权重归一化,确保总和为 1
total = sum(weights.values())
return {k: v / total for k, v in weights.items()} if total else weights
def range_score(value, target, tolerance, weight): # 根据偏差计算得分,允许在一定容差范围内递减
delta = abs(value - target)
return weight * (1 - delta / tolerance) if delta <= tolerance else 0
def score_car(car, spec, w): # 对单辆车进行打分,按需求与权重进行匹配度评估
score = 0.0
w = normalize_weights(w) # 首先归一化权重
try:
lo, hi = car["价格区间"].replace("万人民币", "").split("-") # 提取价格区间
car_price = (int(lo) + int(hi)) * 5000 # 取中值并转为元单位
bmin, bmax = spec["预算区间"]["min"], spec["预算区间"]["max"]
if bmin <= car_price <= bmax:
score += w["预算区间"] # 价格在预算内,加满分
else:
score += range_score(car_price, (bmin + bmax) / 2, 100000, w["预算区间"]) # 不在预算范围内但允许容差
except: pass # 异常跳过价格评分
seat_diff = abs(car["座位数"] - spec["座位数"]) # 计算座位差异
score += w["座位数"] * {0:1.0,1:0.75,2:0.5}.get(seat_diff, -0.1) # 根据差异进行加权打分
if car["用途"] == spec["用途"]: score += w["用途"] # 用途匹配加分
if car["车辆类型"] == spec["车辆类型"]: score += w["车辆类型"] # 类型匹配加分
if car["驱动方式"] == spec["驱动方式"]: score += w["驱动方式"] # 驱动方式匹配加分
if car["动力类型"] == spec["动力类型"]:
score += w["动力类型"] * 2 # 完全匹配动力类型,权重加倍
elif (car["动力类型"], spec["动力类型"]) in [("油电混合", "插电混动"), ("插电混动", "油电混合")]:
score += w["动力类型"] * 1.5 # 类似动力匹配,加权较高分
try:
energy_value = float(car["油耗/电耗"].split()[0]) # 提取能耗数值
if car["动力类型"] in ["纯电", "氢燃料电池"]:
score += range_score(energy_value, spec["能耗上限"], 5, w["能耗上限"]) # 电车能耗评分
score += range_score(int(car["续航/续驶里程"].split()[0]), spec["续航需求_km"], 200, w["续航需求_km"]) # 电车续航评分
else:
score += range_score(energy_value, spec["能耗上限"], 2, w["能耗上限"]) # 油车能耗评分
except: pass # 解析失败则跳过能耗评分
return round(score, 2) # 最终得分保留两位小数
def recommend_car(user_query: str, top_n: int = 3, custom_weights: dict = None, custom_spec: dict = None): # 推荐车辆主函数
if custom_spec is None:
ai_resp = query_kimi(user_query) # 若无自定义需求,调用 Kimi 获取结构化需求
spec = ai_resp["需求"]
else:
spec = custom_spec # 使用传入的需求规范
weights = custom_weights or query_kimi(user_query)["权重"] # 使用自定义或 AI 提供的权重
df = pd.read_json(VEHICLE_DB, orient="records") # 读取车辆数据库为 DataFrame
df["score"] = df.apply(lambda row: score_car(row, spec, weights), axis=1) # 逐行计算得分
return df.sort_values(["score", "价格区间"], ascending=[False, True]).head(top_n)[ # 根据得分和价格排序取前 top_n
["id", "名称", "价格区间", "用途", "车辆类型", "动力类型", "驱动方式", "座位数", "续航/续驶里程", "油耗/电耗", "score"]
]
your_script.py
import os # 操作系统相关模块,用于设置环境变量等
import streamlit as st # 引入 Streamlit 库,用于构建 Web 应用界面
from ai_car_selector_kimi import recommend_car, query_kimi # 从自定义模块导入推荐函数和 Kimi 查询函数
st.set_page_config(page_title="🚘 AI 智能选车助手", layout="centered") # 设置网页标题和布局
st.title("🚗 AI 智能选车助手") # 页面主标题
st.markdown("**请输入你的购车需求**(如:我想买一辆适合家庭出行的中型SUV,预算20万以内,最好油电混合,续航超过800公里):") # 输入提示
user_query = st.text_area("购车需求", height=120) # 多行文本输入框用于填写用户需求
top_n = st.slider("展示前 N 个推荐结果", 1, 10, value=3) # 滑动条选择推荐结果数量
# 自动判断默认单位(简化逻辑处理)
default_unit = "L/100km" # 默认是油耗单位
if any(x in user_query for x in ["纯电", "电动", "电车", "氢", "氢燃料"]): # 如果用户提到电车/氢能源
default_unit = "kWh/100km" # 更换为电耗单位
with st.expander("⚖️ 高级设置:自定义评分权重(可选)"): # 可展开的区域用于设置评分权重
weight_inputs = {
"用途": st.slider("用途 权重", 0, 5, 1), # 各评分项使用滑动条设置权重
"车辆类型": st.slider("车辆类型 权重", 0, 5, 1),
"预算区间": st.slider("预算区间 权重", 0, 5, 1),
"座位数": st.slider("座位数 权重", 0, 5, 1),
"动力类型": st.slider("动力类型 权重", 0, 5, 1),
"驱动方式": st.slider("驱动方式 权重", 0, 5, 1),
"续航需求_km": st.slider("续航需求_km 权重", 0, 5, 1),
"能耗上限": st.slider(f"能耗上限(单位:{default_unit}) 权重", 0, 5, 1),
}
with st.expander("🔐 API Key 设置(当前会话内有效)"): # 可展开区域用于输入自定义 API Key
custom_key = st.text_input("Moonshot API Key", type="password") # 输入框(隐藏密码)
if custom_key: # 如果用户提供了 key
os.environ["MOONSHOT_API_KEY"] = custom_key # 设置为当前环境变量,覆盖默认 key
if st.button("开始智能选车 🚀"): # 点击按钮开始推荐流程
if not user_query.strip(): # 如果输入为空
st.error("请输入购车需求") # 显示错误提示
st.stop() # 停止运行
with st.spinner("正在匹配推荐车型…"): # 显示加载状态提示
try:
ai_resp = query_kimi(user_query) # 调用 LLM 接口获取结构化需求与权重
spec = ai_resp["需求"] # 提取需求字段
if any(val > 0 for val in weight_inputs.values()): # 如果用户自定义了权重
weights = weight_inputs # 使用用户定义权重
st.info("✅ 使用手动设置的评分权重")
else:
weights = ai_resp["权重"] # 否则使用 AI 推荐的权重
st.info("⚠️ 使用 AI 自动推荐权重")
st.subheader("AI 分析出的购车需求:") # 显示需求
st.json(spec) # 用 JSON 格式展示需求
st.write("**使用的评分权重:**") # 展示权重标题
st.json(weights) # 展示权重内容
result_df = recommend_car( # 调用推荐函数
user_query=user_query,
top_n=top_n,
custom_weights=weights,
custom_spec=spec
)
except Exception as e: # 捕获异常
st.error(f"调用失败:{e}") # 显示错误信息
st.stop() # 停止运行
if result_df.empty: # 如果结果为空
st.warning("未找到符合条件的车型,请调整条件重试。") # 提示用户修改条件
else:
st.success("✅ 推荐完成,以下是匹配度最高的车型:") # 成功提示
st.dataframe(result_df.rename(columns={ # 重命名列标题并显示为表格
"id": "车型ID",
"名称": "名称",
"价格区间": "价格",
"用途": "用途",
"车辆类型": "类型",
"动力类型": "动力",
"驱动方式": "驱动",
"座位数": "座位",
"续航/续驶里程": "续航",
"油耗/电耗": "油耗",
"score": "得分"
}), use_container_width=True)
st.download_button( # 提供下载按钮
"📥 下载推荐结果 CSV", # 按钮标题
data=result_df.to_csv(index=False, encoding="utf-8-sig"), # 下载内容为 CSV 格式数据
file_name="car_recommendations.csv", # 下载文件名
mime="text/csv" # 文件类型
)
vehicle_db_1000_named.json(部分如下)
{
"id": "car0001",
"名称": "日产·宝马4系 Pro",
"用途": "越野探险",
"车辆类型": "Coupe",
"价格区间": "56-77万人民币",
"动力类型": "氢燃料电池",
"驱动方式": "两驱",
"座位数": 4,
"续航/续驶里程": "651 km",
"油耗/电耗": "17.1 kWh/100km"
},
{
"id": "car0002",
"名称": "法拉利·GL8 Max",
"用途": "商务接待",
"车辆类型": "MPV",
"价格区间": "245-257万人民币",
"动力类型": "油电混合",
"驱动方式": "前驱",
"座位数": 7,
"续航/续驶里程": "334 km",
"油耗/电耗": "12.9 L/100km"
},
{
"id": "car0003",
"名称": "现代·骐达 Max",
"用途": "商务接待",
"车辆类型": "小型两厢",
"价格区间": "28-51万人民币",
"动力类型": "2.5L汽油",
"驱动方式": "后驱",
"座位数": 5,
"续航/续驶里程": "311 km",
"油耗/电耗": "7.8 L/100km"
}