一、多智能体系统
1.1 受控多智能体 vs 开放式多智能体
开放式的多智能体稳定性太难控制,操作不可预期。
受控多智能体是一种很好的解决方案,虽然会丢失一些灵活性,但从企业场景的稳定性来看,很值得考虑。
先固定再谈论自由,自由可以慢慢赋予、一点点摸索;一上来全让智能体干就无法控制变量来进行后续的优化。
1.2 多智能体通信
多智能体的通信可以使用消息队列,通过事件发布、接收事件来进行各个智能体的隔离。
这样后续添加智能体的时候,比较好扩展。
1.3 子智能体结果控制
受控多智能体的子智能体要进行数据结果的控制。
子智能体 A、子智能体 B 的结果都要产出三类结果:1. 开始、2. 过程、3. 结束。
这样主智能体可以进行横向对比,适合需要多维度对比的场景。
1.4 多智能体框架
CrewAI和DeepAgent都是自主式的多智能体,开放性高。
二、LangGraph
2.1 检查点(Checkpoint)与 StateSnapshot
检查点在用户层面是以StateSnapshot对象存在的,底层是 checkpoint。
StateSnapshot 有
parent_config字段,字段内部有:thread_id:不是线程 id,强调的是一种会话的隔离,记录不同会话的检查点。跨不跨会话是区分 checkpoint 和 store 很重要的一点。checkpoint_id:checkpoint 的唯一性标识。checkpoint_ns:检查点的命名空间,可以区分子图或者图。
StateSnapshot 中有
values字段,用来存储当前状态字典的属性及其属性值。
2.2 Store
Store 中也有 namespace,Store 的 namespace 是由字符串构成的元组,都是用来进行区分的。
Store 可以跨会话,checkpoint 是针对单个会话而言。
两者都可以进行持久化。
2.3 节点定义
定义节点可以用函数,也可以使用对象。
使用对象可以在初始化的时候扩展属性。
使用对象来定义节点需要重写对象的
__call__魔术方法,使用对象()来调用。
2.4 条件边
LangGraph 的条件边组成:
节点
路由函数
映射字典(可选)
路由函数返回的值可以是节点的名称(没有映射字典的情况下)。
有映射字典的情况下,返回的值会在映射字典中进行映射,转换成图中的节点名称。
2.5 中断机制
中断机制需要提前把数据存放到 checkpoint 中,并且要保存
thread_id。保存
thread_id是为了找到哪个会话进行保存,需要先通过thread_id来找到对应的 checkpoint,这样才能恢复。恢复的时候使用
Command(resume=...),传入的值是调用interrupt函数时的返回值。恢复的时候,需要重新执行那个节点,但是会识别出传入了恢复信息,所以不会再次执行
interrupt()函数。但是
interrupt()的前面部分的代码还是会再次执行,尽量保持幂等性,这个地方会造成重复操作需要注意。
三、记忆管理
可以使用 LangGraph 中的Store和checkpoint进行记忆管理。
Store 可以跨会话。
checkpoint 是针对单个会话而言。
较快的获取任务状态可以使用Redis。
保留任务执行的状态,要让模型在完成任务的过程中,知道自己完成了什么,还有什么没有完成。
四、Agent 工作模式
4.1 ReAct 模式
ReAct 是思考 → 行动 → 观察的一个循环。
问题:随着上下文增长,会出现上下文漂移的情况。
目标、决策会被淹没在很长的文本中,注意力会被稀释。
会因为当前任务获取的新文本而夺走注意力,很容易发生漂移。
4.2 Plan and Executor 模式
问题:怎么保证规划就是对的?一开始就错了,后面很大概率会导致错误。
可以边执行,边去查看自己的进度和要完成的目标,进行方向的纠正。
但纠正的一定是正确的吗?还是不确定。
五、JSON 约束
5.1 JSON Schema
使用 JSON Schema 来约束生成 JSON,有部分厂商支持。
这种 Schema 对于类型、枚举等等进行约束,控制模型的幻觉。
是目前约束性很好的一种方法。
5.2 JSON Mode
虽然支持很广,但只能保证是个 JSON 格式。
对于生成的字段是否正确、是否契合业务等等效果并不好。
5.3 提示词约束
可以在提示词中举几个 JSON 的 case,来强化约束。
六、RAG(检索增强生成)
6.1 表格与图片处理
对表格和图片进行语义的提取,在进行检索的时候进行匹配。
表格和图片可以作为一种元数据进行绑定。
过程:
对表格和图片提取语义。
语义进行向量化存储到 chunk。
表格和图片作为一种元数据,在匹配 chunk 的时候,查看是否有对应的元数据,有的话可以进行展示。
图片可以使用MinIO存放,然后展示 URL 在前端。
表格可以在存放的时候用JSON 格式存放,然后放到数据库,前后端协商好定义和展示的形式。
6.2 知识图谱
知识图谱是通过保存实体-关系来打通一些深层关系,可以跨越分片。
比如一个问题和 A、C 都有关系,但语义检索得到 A,A 和 C 是一种深层次的关系,不好获取,使用知识图谱来进行跨分片的查询。
知识图谱的维护、成本也是要考量的关键因素。
流程:
获取用户问题,提取关键实体。
和向量数据库里面的实体数据库进行相似度匹配。
获取到相关的实体。
根据实体去检索图数据库,获取到相关的实体-关系。
6.3 文本处理
使用正则表达式或者大模型识别电话、姓名、关键信息。
对敏感信息进行脱敏。
6.4 RAG 评测
RAG 评测看四个维度:query、检索到的证据、生成的答案、目标答案。
| 指标 | 说明 |
|---|---|
| Answer Relevancy | 答案的相关性,看 query 和生成的答案的匹配度,生成的答案是否很好地回答了 query |
| Faithness | 忠实性,看生成的答案是否基于检索到的证据,防止大模型幻觉 |
| Recall@K | 召回率,top-k 返回的相关性文档块的个数 / 总的相关的文档块个数 |
| Precision@K | 前 K 个位置中相关文档块的个数 / K(当前位置) |
| Context Precision@K | 先计算相关性(相关是 1,不相关是 0),然后和自己的 Precision@K 相乘,最后除以 top-k 中相关的文档总个数 |
| Answer Similarity | 答案的相似度,看生成的答案和目标答案的语义相似程度 |
七、Agent 测试
对于要上线的 Agent 要进行TTFT(首字延迟)测试,测试性能。
测试一组模拟的数据,可以让业务人员进行编写,输入和目标答案来批量测试效果。
八、Skill 体系
以前的 skill 是一种经验的提取总结。
现在的 skill 有脚本、指导、附件等等,慢慢演变成了一种综合的解决方案。
可以使用关键词唤醒skill,或者对 skill 进行语义提取,然后进行语义匹配。
九、Python 相关
9.1 协程(Coroutine)
协程是一种轻量化的线程,但是是被用户所控制的。
在 Python 中,如果像运行正常函数那样
A()运行协程的话,是运行不了的,因为这只是返回一些协程对象,需要创建事件循环放进去。首先要创建事件循环,
asyncio.run()其实默认是创建的,然后create_task()就可以了。但是单纯
create_task()是不行的,因为没有创建事件循环。出现
await就需要把控制权交给事件循环了,等到操作系统通知事件循环它的 IO 完成了,事件循环会把那个协程设置为就绪,等待时机再运行。
9.2 asyncio.to_thread
asyncio.to_thread是把操作放到一个独立的线程,返回一个等待的事件,不会阻塞当前事件循环。因为协程很害怕同步阻塞,一旦出现同步阻塞,整个协程都会停滞,因为这个阻塞不会把控制权交给事件循环,只能硬等。
9.3 闭包
闭包是内部函数访问外部函数的变量,然后外部函数返回内部函数。
这个变量不是全局变量。
内部函数的调用会捆绑外部函数的变量,使得它不会因为外部函数的结束而销毁,可以保持和内部函数一样的生命周期。
9.4 字典的 setdefault
setdefault返回 key 所对应的值。如果没有 key,设置 key 为默认值,返回默认值。
9.5 @lru_cache
@lru_cache会丢弃掉最近最少被使用的对象。可以使用
max_size来控制缓存的最大个数。保存的是参数 → 返回值的映射。
9.6 ContextVar
不同线程之间可以使用 thread 来进行区分。
同一线程的协程可以使用ContextVar来区分。
不同的协程之间 ContextVar 是相互隔离的,可以用来保存请求 id。
十、FastAPI
10.1 Lifespan
lifespan中使用@contextmanager。yield之前是应用启动之前做的。yield之后是结束之后做的。finally一般来进行最后资源的释放。yield是进入应用中执行。
@contextmanager def A(): ... yield 1 ... with A() as num: ...
@contextmanager配合with使用,with中的as是yield的返回值。执行顺序:先执行 A() 内部代码到 yield → 进入 with 代码内部 → 执行完 with 内部代码 → 再执行 A() 中 yield 后面的部分。
10.2 中间件(Middleware)
middleware: 代码执行前的执行动作 call_next() 代码执行后的执行动作
中间件是每次的请求都会执行一次。
lifespan是在应用启动的时候执行一次初始化,结束的时候进行一次清洗。
10.3 异步路由
FastAPI 对于不加
async的路由是创建新的线程。对于加
async的路由是在主线程中的事件循环中运行。
十一、HTTP
11.1 Headers
headers = { "Content-Type": "application/json", "Authorization": "", }Content-Type强调的是请求体的类型是 JSON 格式。
十二、数据结构与算法
12.1 最大堆
每个节点的值都大于等于其子节点的值。
12.2 完全二叉树
完全二叉树的最后一层后面可以空着,但是从左到右必须是没有空隙的。
12.3 B 树 vs B+ 树
B 树:非叶子节点存储着数据和键值。
B+ 树:非叶子节点只存储键值,所以可以存放更多的节点,树的高度要更低,可以减少磁盘 IO。
InnoDB 存储引擎使用 B+ 树,叶子节点使用双向链表,方便范围查询。
B+ 树的节点的键值超过了阈值进行分裂:
左侧数据会在原始的节点。
中间的键值会向上传递。
右侧的键值会放到新创建的节点中。
如果父节点阈值也超了,就会递归分裂。
12.4 LFU
LFU 是淘汰最近使用频率最低的数据。
12.5 K-means
K-means 是一种无监督的聚类算法。
有无监督要看你是否有人工标注的数据。
K-means 是没有人工参与的情况下,进行的一种数据的分类。
十三、数据库
13.1 主键与索引
数据库的主键是一种逻辑的约束,物理层面是以主键索引的形式存在。
主键索引:唯一且非空。
唯一索引:唯一但是可以为空。
13.2 联合索引
联合索引是多个字段组合来进行唯一标识一条记录。
13.3 最左前缀原则
最左前缀原则说的是联合索引,从左往右进行依赖匹配。
比如(A,B,C)的话,就可以匹配 A、AB、ABC 这三种索引类型。
如果是 AC 的话,就会出现中断的问题:A 可以匹配,C 因为中间缺少 B,出现了中断,后面就无法使用索引了。
13.4 覆盖索引
覆盖索引一般是不包括主键索引,因为主键索引根本不需要回表,它的叶子节点本身就有行数据。
覆盖索引一般指的是非主键索引(二级索引),查询这个索引不需要回表到主键索引进行查询,提升查询效率。
比如给 name、age 建立联合索引,然后根据 name 查询 age,联合索引都覆盖了信息,这样就不需要回表查询了。
13.5 选择性
数据库的选择性 = 不同值的记录 / 总的记录。
比如性别:男、女,选择性很低,就不适合作为索引。
13.6 索引使用场景
返回的数据量过大也不会使用索引。
前缀
'%ABC'是无法使用索引的,因为前缀是未知的。'ABC%'可以使用索引,这个前缀是确定性的。
十四、消息队列
14.1 消息堆积
生产者生产的过快或者消费者消费的太慢,导致消息会堆积在消息队列中。
14.2 消息确认机制
消费者消费完消息之后通知消息队列它已经消费成功了。
以防发生重复消费、丢失等情况。
14.3 顺序消费
对于消息队列中很看重顺序的情况,可以使用单消费者的情况。
但是要注意生产者的产生速度,不要太快,不然还是会发生消息堆积的问题。
十五、网络通信
15.1 WebSocket vs SSE
WebSocket:双向通信(全双工)。
SSE:一个长连接的单向通信,服务器会持续向客户端发送数据,但是长连接会比较耗费资源。
15.2 通信模式
全双工:双向通信,可以同时传递消息。
半双工:不能同时传递消息。
单工:单向传递消息。
15.3 SSE 消息格式
data: 内容\n\n
十六、大模型相关
16.1 基础模型 vs Instruct 模型
基础模型:对文本的无脑续写,指令的听从性差。
Instruct 模型:进行了很多指令的微调,去训练模型去听从指令,服从性会更好。
16.2 LoRA
LoRA 更新的时候只会更新低秩矩阵 A、B。
到最后再和参数矩阵相加。
16.3 残差连接
残差连接通过
+X,来使得层数变大的时候,梯度很小了。直接乘的话,会不断接近于 0,所以使用
+X来保持特性。
16.4 SFT(监督微调)
SFT 适合学习好的范式回答和行为。
因为在训练的时候关注的是 output 的 loss。
16.5 RLHF
RLHF 会学习人类更喜欢哪种回答。
之前会训练一个 Reward Model。
Reward Model 是基于 SFT 监督微调的时候生成了很多答案,然后人类进行答案的偏好打分,最后训练出来的。
16.6 DPO
DPO 的 β 越大,会使得 sigmoid 越容易接近上限,容易饱和。
饱和之后就会平缓,loss 越小,越学不到东西,就会很接近原始的模型。
反之亦然,一般选择的参数在 0.1。
16.7 强化学习
State:智能体在环境中做动作所需要的全部信息。
价值函数:未来所有的路线、发生的概率、对应的奖励,全部考虑,然后最后得出的结果。
十七、ClaudeCode 压缩
压缩之前要说清楚自己要压缩的目标,保留关键的决策。
压缩工具输出,只保留工具输出的结论。
有些工具的调用是为了某项子任务,丢弃中间重复的、无用的思考。
没有空间的时候是无法进行压缩的,因为压缩的结果是大模型基于上下文窗口产生的,输入的文本超过了这个窗口,大模型是无法接受的。