Agent Loop 每次循环结束后,其上下文窗口中的对话历史、推理过程、工具调用结果,都会随着会话的关闭而归零。这就像一个每天醒来都失忆的人——昨天你告诉他的项目背景、技术偏好、团队成员,今天他全忘了,你得从头再讲一遍。Memory 系统的存在意义,就是让 Agent 拥有跨会话的持久记忆能力,从“金鱼脑”进化为真正的智能助手。
一、文本变向量
一般使用embedding模型,如Doubao的Doubao-embedding,由字节跳动研发的语义向量化模型,主要面向向量检索的使用场景,支持中、英双语,最长 4K 上下文长度。向量维度 2048 维,支持 512、1024 降维使用。
1 | curl https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal \ |
输出如下,向量就是embedding对应的数据,其实维度很多,我删除了一些方便展示。
1 | {"created":1784698258,"data":{"embedding":[0.00482177734375,0.03955078125,-0.004180908203125,0.045654296875,-0.0031280517578125,0.019287109375,-0.0147705078125,-0.0133056640625,-0.0181884765625,-0.03125,0.0458984375],"object":"embedding"},"id":"021784698257058251fb4d9d2b64b7e0912e9d9de0a598cb39ec9","model":"doubao-embedding-vision-251215","object":"list","usage":{"prompt_tokens":10371,"prompt_tokens_details":{"image_tokens":10224,"text_tokens":147},"total_tokens":10371}} |
这里真的要吐槽一下火山的模型广场,我用文本的embedding模型进行API调用,完全不通,反馈也反馈不了,醉了。
二、向量归一化
每个 embedding 向量都有两个属性:
| 属性 | 含义 | 类比 |
|---|---|---|
| 方向 | 向量指向哪里,代表语义 | 朝东还是朝西 |
| 长度(模长) | 向量有多大,代表向量的长度,就是从坐标原点到向量端点的直线距离。 | 走了 1 米还是 10 米 |
归一化就是:把所有向量的长度都变成 1,让它们站在同一个起跑线上,只比方向。
❓ 为什么要做?
1. 相似度计算更纯粹
- 语义相似 = 方向接近
- 向量长度不应该影响相似度判断
- 归一化后纯靠语义方向匹配,不受模长干扰
2. 计算更快(工程上最重要)
1 | 归一化前:余弦相似度 = 点积 / (|a| × |b|) (要算两次模长) |
向量数据库(FAISS、Milvus 等)对内积索引做了深度优化,归一化后用内积搜索 = 余弦搜索,但速度快很多。
3. 下游任务更稳定
- 聚类、分类、RAG 检索等任务,归一化后效果通常更好
- 避免 “长文本向量模长大、占优势” 这类不公平现象
🛠️ 怎么做?
最常用:L2 归一化(欧几里得归一化)
公式:每个元素 ÷ 向量的模长
1 | import numpy as np |
常见误区
| 误区 | 真相 |
|---|---|
| “API 输出已经归一化了吧?” | 大部分没有(OpenAI、Cohere 等都是原始向量),自己再归一化一遍最稳妥 |
| “归一化后不同模型就能混用了” | ❌ 不行!归一化只统一长度,改变不了语义空间,不同模型的向量还是不能混 |
| “所有场景都要归一化” | 不一定,用欧氏距离的场景就不需要归一化 |
| “归一化会损失信息” | 确实丢了模长信息,但大部分 NLP 场景模长信息没用,方向才是关键 |
三、向量召回
向量召回的本质就是:拿 query 向量,跟库里向量算一次点积,分数最高的 K 个就是结果。
举例
用二维向量举个最直观的例子,我们有:
- 1 个查询向量(用户的问题)
- 3 个文档向量(库里的内容)
- 都是 2 维,并且已经归一化(模长 = 1)
| 向量 | x | y | 说明 |
|---|---|---|---|
| query(查询) | 0.8 | 0.6 | 用户问的问题 |
| doc1 | 0.6 | 0.8 | 文档 1 |
| doc2 | 1.0 | 0.0 | 文档 2 |
| doc3 | 0.0 | 1.0 | 文档 3 |
画在坐标系里大概是这样:
1 | y ↑ |
🧮 点积怎么算?
公式:对应位置相乘,然后加起来。
a⋅b=ax×bx+ay×by
计算 query 和 doc1 的相似度:
1 | query · doc1 = 0.8 × 0.6 + 0.6 × 0.8 |
计算 query 和 doc2 的相似度:
1 | query · doc2 = 0.8 × 1.0 + 0.6 × 0.0 |
计算 query 和 doc3 的相似度:
1 | query · doc3 = 0.8 × 0.0 + 0.6 × 1.0 |
📊 结果
表格
| 文档 | 点积(= 余弦相似度) | 排名 |
|---|---|---|
| doc1 | 0.96 | 🥇 最相似 |
| doc2 | 0.8 | 🥈 第二 |
| doc3 | 0.6 | 🥉 第三 |
所以召回 Top 2 的话,返回 doc1 和 doc2。
生产情况
实际工程上,数据量大了(百万、千万级),暴力算不起,所以用近似最近邻(ANN),牺牲一点点精度,换几百上千倍的速度。
不跟所有向量比,只跟 “可能相似” 的那一小撮比。
就像你在图书馆找书,不会从第一排第一本开始翻,而是先找到对应书架,再在那个书架里找。常见的索引算法
1. HNSW(最常用,效果最好)
层次化小世界图,现在的主流选择(FAISS、Milvus、Pinecone 默认基本都是它)。
原理:
- 把所有向量建成一张图,相似的向量之间连边
- 查询时从入口点出发,沿着边往 “更相似” 的方向跳
- 跳几步就跳到最相似的附近了
类比: 像找朋友的朋友,通过人脉网络快速找到目标
特点:
- 召回率高,速度快
- 内存占用大(图结构占空间)
- 适合对精度要求高的场景
2. IVF(倒排文件)
聚类分桶,先粗筛再精排。
原理:
建库时:把所有向量聚成 N 个簇(比如 1000 个簇),每个簇有一个中心点
查询时:
- 第一步:先找 query 离哪几个簇最近(比如最近的 10 个簇)
- 第二步:只在这 10 个簇里做精确计算
- 不用跟全部 100 万条比,只跟几万条比
类比: 先找到对应书架,再在书架里找书
特点:
- 速度快,内存占用比 HNSW 小
- 精度略低于 HNSW
- 适合数据量特别大的场景
3. PQ(乘积量化)
压缩向量,用空间换精度,适合超大规模数据。
原理:
- 把高维向量切成几段,每段分别量化成一个小编码
- 1536 维的 float 向量(6KB),可能压缩到几十字节
- 计算相似度时用近似距离,不用算完整点积
特点:
- 极度省空间,适合亿级数据
- 精度损失较大,一般和 IVF 配合用(IVFPQ)