Skip to content

2.1 列表与元组:管理 Agent 的消息历史

引言:Agent 的时间线

想象一个客服 Agent 正在与用户对话:

User: "你好"
Agent: "您好!有什么可以帮助您?"
User: "我想查订单"
Agent: "请告诉我您的订单号"
User: "12345"
Agent: "正在为您查询订单 12345..."

这个对话历史需要存储在某个数据结构中,以便 Agent 能够:

  • 记住上下文
  • 回溯历史
  • 生成摘要

列表(List) 是完美的选择——它保持顺序、可以追加、支持索引。

🎯 小白理解指南:什么是"列表"?

列表就像微信聊天记录

  • 每条消息按时间顺序排列(第1条、第2条、第3条...)
  • 你可以随时往下加新消息(追加)
  • 你可以翻到任意一条查看(索引)
  • 你也可以删除某条消息(移除)

在编程中,列表用方括号 [] 表示。比如 ["苹果", "香蕉", "橙子"] 就是一个包含三种水果的列表。

学习目标

  • ✅ 掌握列表的创建和基本操作
  • ✅ 理解列表推导式
  • ✅ 掌握切片操作
  • ✅ 了解元组的不可变特性
  • ✅ 实战:构建消息历史管理器

第一部分:列表基础

创建列表

python
# 空列表
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]

基本操作

python
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

索引和切片

python
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 原列表]

基本语法

python
# 传统方式
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]

带条件的列表推导式

python
# 只保留偶数的平方
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']

嵌套列表推导式

python
# 展平嵌套列表
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]

第三部分:高级列表操作

排序

python
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)

列表拼接和复制

python
# 拼接
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 元组的区别:

  • 列表 []:像白板,可以随时擦掉重写
  • 元组 ():像刻在石头上的字,刻完就不能改了

为什么要用元组?

  1. 更安全:防止重要数据被意外修改(比如坐标、配置)
  2. 更快:因为不能修改,Python 可以做优化
  3. 可以当字典的键:列表不行,元组可以

元组基础

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,)

这就像"一人成团"——你得表明这是个"团",不只是一个人站在那里。

元组的应用场景

python
# 函数返回多个值
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 系统需要不同的格式)
python
"""
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()

本节总结

核心要点

  1. 列表:有序、可变、支持索引和切片
  2. 列表推导式:简洁的列表创建方式
  3. 元组:不可变的列表,更安全
  4. 切片[start:end:step]
  5. 深拷贝 vs 浅拷贝:注意嵌套数据

在 AI Agent 中的应用

操作应用场景
append()添加新消息到历史
[-n:]获取最近 n 条消息
列表推导式过滤和转换消息
sorted()按置信度排序结果
切片管理上下文窗口

下一节:2.2 字典:Agent 状态的核心数据结构

基于 MIT 许可证发布。内容版权归作者所有。