2.1 列表与元组:管理 Agent 的消息历史
引言:Agent 的时间线
想象一个客服 Agent 正在与用户对话:
User: "你好"
Agent: "您好!有什么可以帮助您?"
User: "我想查订单"
Agent: "请告诉我您的订单号"
User: "12345"
Agent: "正在为您查询订单 12345..."这个对话历史需要存储在某个数据结构中,以便 Agent 能够:
- 记住上下文
- 回溯历史
- 生成摘要
列表(List) 是完美的选择——它保持顺序、可以追加、支持索引。
🎯 小白理解指南:什么是"列表"?
列表就像微信聊天记录:
- 每条消息按时间顺序排列(第1条、第2条、第3条...)
- 你可以随时往下加新消息(追加)
- 你可以翻到任意一条查看(索引)
- 你也可以删除某条消息(移除)
在编程中,列表用方括号
[]表示。比如["苹果", "香蕉", "橙子"]就是一个包含三种水果的列表。
学习目标
- ✅ 掌握列表的创建和基本操作
- ✅ 理解列表推导式
- ✅ 掌握切片操作
- ✅ 了解元组的不可变特性
- ✅ 实战:构建消息历史管理器
第一部分:列表基础
创建列表
# 空列表
messages: list = []
messages: list[str] = [] # 带类型注解
# 带初始值的列表
messages = ["Hello", "Hi", "How are you?"]
# 使用 list() 构造函数
numbers = list(range(5)) # [0, 1, 2, 3, 4]
# 列表可以包含不同类型(但不推荐)
mixed = [1, "hello", 3.14, True]基本操作
messages = ["Hello", "Hi"]
# 追加元素
messages.append("Good morning")
print(messages) # ['Hello', 'Hi', 'Good morning']
# 插入元素
messages.insert(1, "Hey") # 在索引 1 处插入
print(messages) # ['Hello', 'Hey', 'Hi', 'Good morning']
# 删除元素
messages.remove("Hey") # 删除第一个匹配的元素
print(messages) # ['Hello', 'Hi', 'Good morning']
# 弹出元素
last = messages.pop() # 删除并返回最后一个元素
print(last) # 'Good morning'
print(messages) # ['Hello', 'Hi']
# 扩展列表
messages.extend(["See you", "Bye"])
print(messages) # ['Hello', 'Hi', 'See you', 'Bye']
# 获取长度
print(len(messages)) # 4
# 检查成员
print("Hello" in messages) # True索引和切片
messages = ["Hello", "Hi", "Hey", "Good morning", "Bye"]
# 正向索引(从 0 开始)
print(messages[0]) # 'Hello'
print(messages[2]) # 'Hey'
# 负向索引(从 -1 开始)
print(messages[-1]) # 'Bye'(最后一个)
print(messages[-2]) # 'Good morning'(倒数第二个)
# 切片 [start:end:step]
print(messages[1:3]) # ['Hi', 'Hey'](索引 1 到 2)
print(messages[:2]) # ['Hello', 'Hi'](前两个)
print(messages[2:]) # ['Hey', 'Good morning', 'Bye'](从索引 2 到末尾)
print(messages[::2]) # ['Hello', 'Hey', 'Bye'](每隔一个)
print(messages[::-1]) # 反转列表💡 关键概念:切片创建新列表,不修改原列表。
🎯 小白理解指南:索引和切片
索引就像电影院座位号:
- Python 从 0 开始数(第一个是 0 号,不是 1 号!)
messages[0]就是"第一个元素"messages[-1]是从后往前数的"最后一个"(负数表示倒着数)切片就像复印机复印指定页数:
messages[1:3]= "从第2个到第3个"(注意:不包括结束位置!)messages[:2]= "前2个"messages[2:]= "从第3个开始到最后"messages[::-1]= "全部倒过来"(很常用的技巧!)记住:切片是复制一份新的,不会改变原来的列表。
第二部分:列表推导式
🎯 小白理解指南:什么是列表推导式?
列表推导式是 Python 的"一行魔法"——把原本需要好几行的代码压缩成一行。
想象你有一堆苹果,想把每个苹果都削皮:
- 传统方式:拿一个空篮子,一个一个削,削完放进篮子
- 列表推导式:一句话:"给我一篮子削好皮的苹果"
语法模板:
[对每个元素做什么 for 元素 in 原列表]
基本语法
# 传统方式
squares = []
for i in range(10):
squares.append(i ** 2)
# 列表推导式(更 Pythonic)
squares = [i ** 2 for i in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]带条件的列表推导式
# 只保留偶数的平方
even_squares = [i ** 2 for i in range(10) if i % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
# AI 应用:过滤有效消息
messages = ["Hello", "", "World", "", "AI"]
valid = [msg for msg in messages if msg] # 过滤空字符串
print(valid) # ['Hello', 'World', 'AI']
# 转换消息格式
raw_messages = ["hello", "world"]
formatted = [f"User: {msg.upper()}" for msg in raw_messages]
print(formatted) # ['User: HELLO', 'User: WORLD']嵌套列表推导式
# 展平嵌套列表
nested = [[1, 2], [3, 4], [5, 6]]
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5, 6]
# AI 应用:展平对话批次
batches = [
[{"role": "user", "content": "Hi"}],
[{"role": "assistant", "content": "Hello"}],
]
all_messages = [msg for batch in batches for msg in batch]第三部分:高级列表操作
排序
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sorted() - 返回新列表
sorted_nums = sorted(numbers)
print(sorted_nums) # [1, 1, 2, 3, 4, 5, 6, 9]
# reverse=True 降序
desc = sorted(numbers, reverse=True)
# key 参数:自定义排序
words = ["apple", "pie", "a", "longer"]
by_length = sorted(words, key=len)
print(by_length) # ['a', 'pie', 'apple', 'longer']
# AI 应用:按置信度排序
results = [
{"text": "Result 1", "confidence": 0.8},
{"text": "Result 2", "confidence": 0.95},
{"text": "Result 3", "confidence": 0.7},
]
sorted_results = sorted(results, key=lambda x: x["confidence"], reverse=True)列表拼接和复制
# 拼接
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2 # [1, 2, 3, 4, 5, 6]
# 重复
repeated = [0] * 5 # [0, 0, 0, 0, 0]
# 浅拷贝 vs 深拷贝
original = [1, [2, 3], 4]
# 浅拷贝
shallow = original.copy() # 或 original[:]
shallow[1][0] = 999
print(original) # [1, [999, 3], 4](嵌套列表被修改!)
# 深拷贝
import copy
deep = copy.deepcopy(original)
deep[1][0] = 777
print(original) # [1, [999, 3], 4](不受影响)🎯 小白理解指南:浅拷贝 vs 深拷贝
想象你有一个文件夹,里面有一些文件和一个子文件夹:
浅拷贝就像复制快捷方式:
- 外层的文件夹是新的
- 但子文件夹只是创建了一个"快捷方式",指向原来的位置
- 修改子文件夹里的东西,原来的也会变!
深拷贝就像完全复制所有内容:
- 文件夹是新的,子文件夹也是新的
- 两边完全独立,互不影响
什么时候用深拷贝? 当你的列表里面还有列表(嵌套结构)时,想要完全独立的副本就用
copy.deepcopy()。
第四部分:元组(Tuple)
🎯 小白理解指南:什么是元组?
元组就像打印好的身份证——一旦制作完成,就不能修改了。
列表 vs 元组的区别:
- 列表
[]:像白板,可以随时擦掉重写- 元组
():像刻在石头上的字,刻完就不能改了为什么要用元组?
- 更安全:防止重要数据被意外修改(比如坐标、配置)
- 更快:因为不能修改,Python 可以做优化
- 可以当字典的键:列表不行,元组可以
元组基础
# 元组是不可变的列表
coordinates = (40.7128, -74.0060)
rgb = (255, 128, 0)
# 元组解包
lat, lon = coordinates
print(f"纬度: {lat}, 经度: {lon}")
# 单元素元组(注意逗号!)
single = (42,) # 正确
not_tuple = (42) # 这是 int,不是元组!
# 为什么使用元组?
# 1. 性能:比列表略快
# 2. 安全:不可变,防止意外修改
# 3. 可作为字典键(列表不行)🎯 小白易错点:单元素元组
(42)不是元组!Python 认为这只是数学中的括号,结果是整数42。要创建只有一个元素的元组,必须加逗号:
(42,)这就像"一人成团"——你得表明这是个"团",不只是一个人站在那里。
元组的应用场景
# 函数返回多个值
def get_user_info():
return "Alice", 25, "alice@example.com"
name, age, email = get_user_info()
# 用作字典键
location_data = {
(40.7128, -74.0060): "New York",
(51.5074, -0.1278): "London",
}
# 不可变的配置
DEFAULT_CONFIG = (
"gpt-3.5-turbo", # model
0.7, # temperature
2000, # max_tokens
)第五部分:实战 - 消息历史管理器
🎯 小白理解指南:为什么需要消息历史管理器?
想象你是一个客服,用户说:"我要退上次买的那个东西"。
如果你不记得"上次买的是什么",你就没法帮他退货!
消息历史管理器就是帮 AI 记住对话内容的工具:
- 记录每一轮对话(谁说了什么)
- 控制记忆长度(不能记太多,否则会"爆内存")
- 支持搜索和筛选(快速找到关键信息)
- 转换格式(不同的 AI 系统需要不同的格式)
"""
Agent 消息历史管理器
演示列表在 AI Agent 中的实际应用
"""
from typing import List, Dict, Optional, Literal
from dataclasses import dataclass
from datetime import datetime
@dataclass
class Message:
"""消息数据类"""
role: Literal["user", "assistant", "system"]
content: str
timestamp: datetime
metadata: Optional[Dict] = None
class MessageHistory:
"""消息历史管理器"""
def __init__(self, max_messages: int = 100):
"""
初始化消息历史
Args:
max_messages: 最大保存消息数
"""
self.messages: List[Message] = []
self.max_messages = max_messages
def add_message(
self,
role: Literal["user", "assistant", "system"],
content: str,
metadata: Optional[Dict] = None
) -> None:
"""
添加消息
Args:
role: 消息角色
content: 消息内容
metadata: 元数据
"""
message = Message(
role=role,
content=content,
timestamp=datetime.now(),
metadata=metadata
)
self.messages.append(message)
# 保持在最大限制内
if len(self.messages) > self.max_messages:
self.messages = self.messages[-self.max_messages:]
def get_recent(self, n: int = 10) -> List[Message]:
"""
获取最近的 n 条消息
Args:
n: 消息数量
Returns:
消息列表
"""
return self.messages[-n:]
def get_by_role(self, role: str) -> List[Message]:
"""
按角色筛选消息
Args:
role: 消息角色
Returns:
筛选后的消息列表
"""
return [msg for msg in self.messages if msg.role == role]
def search(self, keyword: str) -> List[Message]:
"""
搜索包含关键词的消息
Args:
keyword: 搜索关键词
Returns:
包含关键词的消息列表
"""
return [
msg for msg in self.messages
if keyword.lower() in msg.content.lower()
]
def get_context_window(
self,
max_tokens: int = 2000,
tokens_per_message: int = 50
) -> List[Message]:
"""
获取符合 token 限制的上下文窗口
Args:
max_tokens: 最大 token 数
tokens_per_message: 每条消息平均 token 数
Returns:
上下文消息列表
"""
max_messages = max_tokens // tokens_per_message
return self.get_recent(max_messages)
def to_langchain_format(self) -> List[Dict[str, str]]:
"""
转换为 LangChain 消息格式
Returns:
LangChain 格式的消息列表
"""
return [
{
"role": msg.role,
"content": msg.content
}
for msg in self.messages
]
def clear(self) -> None:
"""清空消息历史"""
self.messages = []
def get_summary(self) -> Dict:
"""
获取历史摘要
Returns:
摘要信息
"""
total = len(self.messages)
by_role = {}
for msg in self.messages:
by_role[msg.role] = by_role.get(msg.role, 0) + 1
return {
"total_messages": total,
"by_role": by_role,
"first_message": self.messages[0].timestamp if self.messages else None,
"last_message": self.messages[-1].timestamp if self.messages else None,
}
# 使用示例
def main():
"""演示消息历史管理器"""
history = MessageHistory(max_messages=50)
# 添加对话
history.add_message("user", "你好")
history.add_message("assistant", "您好!有什么可以帮助您?")
history.add_message("user", "天气怎么样?")
history.add_message("assistant", "让我为您查询天气信息...")
history.add_message("user", "谢谢")
# 获取最近消息
recent = history.get_recent(3)
print(f"最近 3 条消息: {len(recent)}")
# 按角色筛选
user_messages = history.get_by_role("user")
print(f"用户消息数: {len(user_messages)}")
# 搜索
results = history.search("天气")
print(f"包含'天气'的消息: {len(results)}")
# 获取摘要
summary = history.get_summary()
print(f"\n摘要: {summary}")
# 转换为 LangChain 格式
langchain_messages = history.to_langchain_format()
print(f"\nLangChain 格式: {langchain_messages[:2]}")
if __name__ == "__main__":
main()本节总结
核心要点
- 列表:有序、可变、支持索引和切片
- 列表推导式:简洁的列表创建方式
- 元组:不可变的列表,更安全
- 切片:
[start:end:step] - 深拷贝 vs 浅拷贝:注意嵌套数据
在 AI Agent 中的应用
| 操作 | 应用场景 |
|---|---|
append() | 添加新消息到历史 |
[-n:] | 获取最近 n 条消息 |
| 列表推导式 | 过滤和转换消息 |
sorted() | 按置信度排序结果 |
| 切片 | 管理上下文窗口 |