☰
LLM记忆驱动的驾驶路线规划:从偏好建模到Agent落地的完整实践
2026/10/15 7:00:38 网站建设 项目流程

这个项目名一看就很有意思——“Driving directions from LLM memory”,从LLM的记忆里拿驾驶路线。听起来很玄乎,实际跑通之后你会发现,这其实是Agent类应用里非常典型的一种落地形态:让大模型记住用户的长期偏好,再结合外部地图能力把模糊意图转化成精确指令。我花了一周多时间把这个思路从零搭成了一个可用的Demo,中间踩了不少坑,也总结出了几个关键设计点。这篇东西就把整个项目的思路、技术拆解、实操代码和排查经验一次性讲清楚,给同样对LLM记忆系统感兴趣的朋友一个可以直接抄作业的参考。

1. 项目思路:为什么要把路线指引交给LLM记忆

1.1 传统路线规划的痛点

现在打开任何一款主流地图应用,路线规划的逻辑本质上都是一套规则加优化算法:输入起点终点,系统根据路况、拥堵、距离、时间预算算出一条或多条候选路线,再让你自己挑。这套流程在大多数时候够用,但你仔细想想,它其实一点都不“懂你”。

比如我每天通勤,从家到公司有两条路,一条走主干道,距离短但早晚高峰很容易堵;另一条绕一点,但沿路信号灯少,反而平均耗时更稳定。导航每次都让我选,甚至默认推荐距离最短的那条。每次我都要手动点几下才能切到“我的路线”。再比如说,我周末常去某个商场,导航里存的地址是商场正门,可我习惯的路线是从地下车库某个入口进去,这种细粒度的偏好,传统地图应用几乎没有表达方式。

这背后的根本问题在于:传统导航的交互模型是“一次性查询”,它不保存你的历史选择,不学习你的偏好,更不会在你说“今天走老路”的时候自动理解。所有信息都需要你明确告诉它,哪怕你已经用了它三五年。

1.2 LLM记忆带来的交互范式转变

“Driving directions from LLM memory”这个项目的出发点就是想把交互范式换掉。让LLM作为路线的“大脑代理”,它拥有一个记忆系统,能记住你过去的行为、选择、习惯,然后你用一句很自然的会话式指令——比如“去公司,别走高架”——它就能从记忆里提取出真正符合你需求的路线方案,再调用地图服务去执行。

这里有两个核心转变:

第一个转变是从“用户迁就机器”变成“机器适配用户”。以前是我必须按照地图应用提供的输入格式去给出起点终点,现在只需要说一句话,剩下的大量隐性信息——具体的起点坐标、常用路线、偏好路段——由LLM结合记忆自动补全。

第二个转变是从“单次查询”变成“持续记忆”。每次导航行为结束后,系统会把路线选择、时间、交通状况、你的最终决定(比如你有没有中途切换路线)写回记忆存储。日积月累,这个系统越来越像一个熟悉你的私人司机:他知道你着急的时候会选高速,知道你在雨天的备选方案,知道你新搬了家之后最常去的位置。

选择记忆方案还有一个很重要的原因是成本效率。你不可能把一个人几个月的历史路线数据全部塞进上下文窗口里,那样既不经济也不可靠。通过内存检索的方式,系统只在一句新指令到达时从记忆库中取回最相关的几十条记录,这个量级对LLM来说是完全没有压力的。

2. 核心技术拆解:LLM记忆的构建与应用

2.1 短期记忆与长期记忆的工程实现

在设计记忆系统时,我参考了认知科学里的“工作记忆”和“长期记忆”分类,并把它映射到工程结构上。

短期记忆在这个项目里对应的是当前对话会话的内部状态。比如用户刚刚说过“我今天要去机场”,在接下来几轮里,“机场”这个目的地就需要保持在活动上下文中。实现上我用了最常规的方式:让LLM在处理输入时携带一段标准的会话历史,同时用一个JSON结构专门存放“当前行程状态”,包括当前起点、终点、携带乘客等关键字段。

长期记忆则完全不同。它需要跨越会话保存,核心是结构化和向量化两类记录:

  • 结构化记录:包含用户常去地点、常用路线、偏好时间段、路线选择历史。这些适合用键值对或表格存储,查询条件很明确。
  • 向量化记录:包含自然语言形式的偏好描述,比如“上次下雨天为了不晒选择了一条林荫路”,这类数据无法通过简单的SQL筛选,需要转换为向量,用相似度检索。

我在项目里使用的是SQLite加向量检索配套的方案。SQLite存储结构化数据轻量可靠,向量部分则借助本地嵌入模型生成特征向量,再用一个支持余弦相似度的索引来检索。没有依赖重型数据库,因为Demo阶段更关注逻辑闭环。

2.2 从记忆到路线:上下文检索与偏好建模

这一步是整个系统的核心环节。当用户说了一句新指令,系统要决定调用哪些记忆片段来辅助决策。

我当时把流程设计成三个子步骤。第一步是意图识别,用LLM判断用户是在查询历史记录、规划新路线还是修改已有行程。第二步是记忆召回,根据意图中的地点、时间、天气、动作等信息,从记忆存储中检索相关片段,比如用户过去在这个时间段去过某个地点,或者曾经表达过对某类路段的偏好。第三步是路线生成,把召回的片段和当前请求一起封装进提示词,让LLM输出结构化的路线请求参数。

偏好建模这里花了很多功夫。用户不会直接说“记录一下,我喜欢走江边路”,更多时候是从行为里推断出来的。我实现了一个“行为推断模块”:每一次行程结束后,将系统推荐路线和用户实际选择进行对比,如果用户偏离了推荐路线,就提取这条实际路线的特征(路段性质、是否为避开拥堵的替代路、通行时间)写进记忆库。比如有两次用户在傍晚选择了一条较长的滨河路,而这条路线比推荐路线平均多花八分钟,系统就会生成一条隐含偏好:“傍晚时段用户倾向于选择滨河路线,可能与景观较好或拥堵躲避有关。”

这样偏好就不是用户显式告诉LLM的,而是系统从行为里“挖”出来的。当然,这种推断不一定永远正确,所以项目里还保留了用户确认的交互环节:当系统检索到强相关的偏好记录但置信度不高时,会先向用户确认“是否仍按之前傍晚经常走的滨河路线?”

3. 实操实现:从零搭建记忆驱动的路线指引Demo

3.1 环境准备与数据组织

先说明一下我搭建Demo时用的技术栈,整体偏轻量,方便快速验证思路:

  • LLM推理:本地部署的量化开源LLM模型,通过OpenAI兼容接口调用
  • 嵌入模型:一个轻量的中文文本嵌入模型,用于把偏好描述转成向量
  • 向量存储:使用sqlite-vec扩展,在SQLite表里直接建向量索引
  • 地图服务:调用某地图服务的路线规划API,用于真实路网数据

数据组织上,我在SQLite里建了三张核心表:

-- 地点表:存储用户常去地点的语义信息 CREATE TABLE places ( id INTEGER PRIMARY KEY, name TEXT, address TEXT, lat REAL, lng REAL, tag TEXT, created_at TIMESTAMP ); -- 行程记录表:存储每次导航会话的路线与结果 CREATE TABLE trips ( id INTEGER PRIMARY KEY, start_place_id INTEGER, end_place_id INTEGER, route_choice TEXT, traffic_condition TEXT, departure_time TIMESTAMP, actual_duration INTEGER, preferred INTEGER, note TEXT, FOREIGN KEY(start_place_id) REFERENCES places(id), FOREIGN KEY(end_place_id) REFERENCES places(id) ); -- 偏好记忆表:同时存原文和向量 CREATE TABLE preferences ( id INTEGER PRIMARY KEY, content TEXT, embedding BLOB, source_trip_id INTEGER, confidence REAL, created_at TIMESTAMP );

建好这三张表之后,整个系统的基础就有了。后面所有记忆的写入、检索都在这些表上展开。注意trips表里的preferred字段非常关键,它标记了这次行程是否是用户最终确认采纳的方案,只有被标记为preferred=1的记录才会被用来生成新的偏好记忆。

3.2 核心代码实现与参数说明

下面的代码是记忆写入侧的简化版本。每次导航结束后,系统会对比推荐路线和实际路线,如果用户实际走的路线和推荐路线不一致,就会触发偏好生成逻辑。

import json import sqlite3 from datetime import datetime def record_trip(user_id, start_place, end_place, rec_route, actual_route, traffic): """ 记录一次行程,并判断是否生成本地偏好。 rec_route: 系统推荐路线的描述 dict actual_route: 用户实际走的路线的描述 dict traffic: 当时的交通状况字符串 """ conn = sqlite3.connect("memory.sqlite") cur = conn.cursor() # 写入行程记录 cur.execute(""" INSERT INTO trips (start_place_id, end_place_id, route_choice, traffic_condition, departure_time, actual_duration) VALUES (?, ?, ?, ?, ?, ?) """, ( start_place["id"], end_place["id"], json.dumps(actual_route, ensure_ascii=False), traffic, datetime.now().isoformat(), actual_route["duration_minutes"] )) trip_id = cur.lastrowid # 比较推荐路线和实际路线,判断是否需要生成偏好 is_preferred = 0 if rec_route["route_id"] != actual_route["route_id"]: # 用户实际选择偏离了推荐,说明有潜在偏好 preference_text = build_preference_text( start_place, end_place, rec_route, actual_route, traffic ) # 生成向量并写入偏好表 embedding = generate_embedding(preference_text) cur.execute(""" INSERT INTO preferences (content, embedding, source_trip_id, confidence, created_at) VALUES (?, ?, ?, ?, ?) """, ( preference_text, embedding.tobytes(), trip_id, 0.7, datetime.now().isoformat() )) is_preferred = 1 cur.execute("UPDATE trips SET preferred = ? WHERE id = ?", (is_preferred, trip_id)) conn.commit() conn.close() return trip_id def build_preference_text(start_place, end_place, rec_route, actual_route, traffic): """构造偏好描述的文本,供LLM和向量检索使用""" return ( f"用户从{start_place['name']}到{end_place['name']}," f"在{traffic}条件下,没有选择系统推荐的{rec_route['summary']}," f"而是选择了{actual_route['summary']}," f"实际耗时{actual_route['duration_minutes']}分钟。" )

上面代码的注释我想多说几句。这里比较推荐路线和实际路线用的是route_id,这是一个字符串标识,每条路线由地图服务生成时就有唯一的ID。不要拿路线名称去比较,不同服务商返回的名称格式很不一致。还有confidence=0.7是我给这个偏好记录预设的置信度,不属于用户显式确认,因此置信度不会很高,后续在召回时需要结合用户反馈调整权重。

下一个关键部分是记忆召回与提示词组装。当用户说“去公司”时,系统要检索出相关的历史语义:

def recall_relevant_memories(request_text, top_k=10): """ 根据当前请求文本,从偏好记忆库中召回最相关的记录。 """ query_embedding = generate_embedding(request_text) conn = sqlite3.connect("memory.sqlite") # 使用sqlite-vec的向量搜索语法 rows = conn.execute(""" SELECT id, content, distance FROM preferences WHERE embedding MATCH ? ORDER BY distance LIMIT ? """, (query_embedding.tobytes(), top_k)).fetchall() # 过滤掉距离超出阈值的记忆片段 relevant = [ {"id": r[0], "content": r[1]} for r in rows if r[2] < 0.35 ] conn.close() return relevant def compose_prompt(user_input, place_info, recalled_memories): memory_block = "\n".join( f"- 回忆 {m['id']}: {m['content']}" for m in recalled_memories ) prompt = f""" 你是用户的路线规划助手。下面是从用户历史记忆中检索到的相关偏好: {memory_block} 用户当前说:{user_input} 已知用户地点信息: {json.dumps(place_info, ensure_ascii=False)} 请根据用户历史偏好,结合当前请求,输出一个结构化的路线规划请求,包含: - start_lat, start_lng - end_lat, end_lng - waypoints(途径点列表,根据历史偏好可以自行添加) - route_preference(可选值:fastest, stable, scenic, avoid_highway) - human_readable_reason(说明为什么这样规划) 只输出JSON对象。 """ return prompt

这里distance < 0.35的阈值是我经过二十多组测试后确定的经验值,不同嵌入模型的最佳阈值差别很大,建议你在自己的环境里做一次阈值扫描。方法是准备一批已知相关的记忆对和一批完全不相关的干扰项,计算它们各距离值的分布,取两条分布曲线交叉点的距离作为阈值。

3.3 关键优化与调试技巧

把记忆系统接入LLM之后,遇到的最大问题就是“提示词越长,模型越容易迷糊”。我最初把所有召回的记忆都拼进提示词,结果模型经常混淆不同时间段的路线选择,甚至把昨天去商场的记忆安到今天上班的行程上。后来我加了一个“记忆筛选小模型”:让嵌入模型先做一次粗筛,再用LLM对粗筛后的记忆做一次相关性判别,只保留真正与当前请求语义一致的片段。

另一个优化是关于意图识别的稳定性。最初我直接用LLM做自由文本意图解析,输出常常不固定,后来改成few-shot方式,在提示词里给出三个示例,并明确要求只输出预设的几个枚举值。输出准确率一下子从七成提到了九成以上。如果要处理更多意图类型,可以做一个简单的分类器微调,但Demo阶段用few-shot完全够用。

还有一个细节是坐标系处理。地图服务返回的路线会经过多个路段,每个路段有大量坐标点。把这些原始坐标全部存进记忆是浪费空间。我在实现时只保存了“特征点”——路线的起点、终点、中途转向点和路线所属的“廊道”。所谓廊道就是一条宽几十米的带状范围,这样即使用户有不同的起终点,也能判断两条路线是否大致沿着同一走廊。

4. 常见问题与排查技巧实录

4.1 记忆过期与路线失效

用户地址会变,道路也会变,记忆如果不做时效管理,就可能给出过时甚至危险的指引。项目里会遇到这样的情况:用户三个月前经常去某老地址,今天突然说“去老地方”,系统从记忆中检索到旧地址,推荐了一条已经封闭的道路。

这个问题靠一条策略解决:时间衰减。给每条记忆记录一个时间戳和一个衰减系数,在做向量召回时,把记忆的原始相似度乘上一个时间衰减因子。我实现的经验是:

import math from datetime import datetime, timezone def time_decayed_score(score, created_at, half_life_days=30): age_days = (datetime.now(timezone.utc) - created_at).days decay = 0.5 ** (age_days / half_life_days) return score * decay

这里half_life_days=30意味着每一篇记忆在三十天后相关性减半。如果某篇记忆被用户后续行为再次确认,则会刷新时间戳,相当于“强化记忆权重”。这个机制和人类记忆的遗忘曲线有异曲同工之处,实现起来也不复杂,但它直接让系统的召回结果从“偶尔莫名其妙”变得可靠得多。

4.2 上下文污染与多轮对话管理

初学者很容易忽略一个细节:长对话里,前面几轮提到的中间地点会在用户说完新需求后仍然留在上下文里,被LLM误当成新路线的途经点。比如用户先说“先送孩子去学校”,然后说“再到公司”,系统很可能把“学校”设定为从家到公司路线的强制途经点,哪怕早上并不顺路。

我解决这个问题是在提示词里设置一个“行程状态重置”机制。每当用户启用一个新的导航请求,系统就清空上一轮对话中的地点状态,仅保留明确的“继续”“加上”这类延续性指令才考虑合并。代码里就是一个reset_context标记,在生成新路线前判断是否重置。

还有一种更隐蔽的污染:偏好记忆之间互相矛盾。用户某两周一直走高速,后来换成国道,旧记忆并没有被删除,下一次指令把两种记忆都集齐了,模型就变得犹豫不决。我在处理时加入一个“最新偏好优先”的规则,在提示词里特别注明“若存在与最近五天内的偏好冲突的旧记忆,以最近为准”。

4.3 实测表现与效果评估

整个Demo在十几个测试场景里跑下来的结果,让我对这套方案有了更实感的判断。我把测试分成三类:一是通勤场景,二是弹性出行场景,三是临时新地点场景。

通勤场景效果最好。因为我事先注入了足够多的历史行程数据,系统在用户说“去公司”时能准确从记忆中调出常用路线,并给出合理的替代方案。比如用户平时走主干道,但今天下雨,系统从偏好表里检索到“雨天倾向于走避免拥堵的滨河路”,于是推荐了一条替代路线。

弹性出行场景表现也不错,比如“找个这周末能带狗去的地方”,系统检索相关偏好后能结合地图搜索结果返回候选地址。但语义匹配的精度还有提升空间,偶尔会召回完全无关的记忆。

临时新地点场景是最弱的,因为记忆库里没有任何相关信息,系统退化为普通的路线规划器,优势不明显。

用数据说话的话,我标注了三十条测试用例,通勤场景的正确率在86%左右,弹性场景只有70%,临时场景基本和传统导航一致。这个结果表明,记忆驱动的路线规划适合高频、强习惯性出行场景,低频或首次去的地方,传统方式反而更省事。

5. 给同样在做LLM记忆应用的人一些建议

整个项目跑下来,我最大的感受是“LLM记忆”这个技术方向的价值不在于模型本身有多强,而在于你怎么设计写和读两侧的逻辑。写的时候不能只依赖用户显式表达,要用系统性的行为追踪去挖掘隐含偏好;读的时候要用向量检索加上规则筛选,而不是把所有记忆一股脑丢进上下文。

如果你打算复现类似的项目,我建议从单场景做起,先把“通勤偏好”这一个维度做透。用一到两星期的历史导航数据灌进记忆库,然后跑用户日常对话,观察系统做出的路线建议是否符合实际习惯。这一关过了,再考虑多场景、多用户的并发情况。

最后分享一个踩坑后的心得:千万不要跳过用户确认这一层。我最初把偏好推断做成全自动,认为系统推荐即正确,结果有几次用户在界面里手动切回了默认路线,系统记录下来的偏好反而变成了噪音。后来我在生成新偏好时加入一条隐式确认规则——用户连续两次走同一偏离路线才写入高置信度的偏好记忆——整个系统的表现才稳定下来。有时候,给机器一点“怀疑的空间”,反而比盲目自信更实用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询