Ai

hello Agent

上传于 2026年9月3日50 个章节

hello Agent

50 个章节

从智能体定义、语言模型和 Agent 范式,到记忆、检索、协议、训练与评估的系统学习笔记。

  • #Agent
  • #LLM
  • #RAG
  • #LangChain
  • #LangGraph

一、智能体

1、什么是智能体

智能体,英文是 Agent,可以理解为: 能够感知环境、进行决策,并采取行动完成目标的系统 智能体通常包含四个要素:

要素含义
环境智能体所在的外部世界
传感器获取信息的方式
执行器执行动作的方式
自主性能根据目标自行决策

智能体不是只会被动回答问题的程序,而是能够根据环境变化主动行动的系统

2、智能体的主要类型

传统智能体可以分为几类:

类型特点
简单反射智能体根据固定规则快速反应
基于模型的智能体有内部状态,能记住部分环境信息
基于目标的智能体根据目标选择行动
基于效用的智能体在多个方案中选择最优方案
学习型智能体能通过经验不断改进

例如,导航系统就是一个基于目标和效用的智能体:它不仅要到达目的地,还会综合考虑距离、时间、拥堵情况等因素

3、LLM 驱动的智能体

大语言模型出现后,智能体进入了新的阶段。 传统智能体依赖人工设计规则,而 LLM 智能体可以直接理解自然语言任务,并进行推理、规划和工具调用。

例如用户输入:

帮我规划一次旅行

LLM 智能体可能会自动拆解为:

理解需求 → 查询天气 → 搜索景点 → 制定路线 → 输出行程

所以,LLM 智能体更像是一个会思考、会规划、会调用工具的助手。

4、PEAS 模型

PEAS 是描述智能体任务环境的方法:

缩写含义
PPerformance,性能指标
EEnvironment,环境
AActuators,执行器
SSensors,传感器

例如旅行智能体:

PEAS示例
性能指标行程合理、预算合适、用户满意
环境天气、交通、酒店、景点
执行器查询 API、搜索网页、生成方案
传感器用户输入、搜索结果、工具反馈

PEAS 的作用是帮助我们明确:智能体要完成什么任务、面对什么环境、能做什么动作。

5、Agent Loop

智能体的核心运行方式是循环:

感知 → 思考 → 行动 → 观察 → 再次思考

对应到 LLM 智能体中,常见形式是:

Thought:我需要先查询天气
Action:调用天气工具
Observation:天气晴,气温25℃

这个循环让智能体可以一边执行任务,一边根据反馈调整下一步。

二、智能体发展史

1、发展

符号主义 → 专家系统 → 早期智能体 → 强化学习 → 预训练模型 → LLM 智能体

每一代智能体,都是为了解决上一代方法的局限。

2、符号主义 AI

早期人工智能主要受符号主义影响。 符号主义认为:智能就是对符号和规则进行推理。 例如:

IF 天空有乌云 AND 湿度高
THEN 可能要下雨

优点是逻辑清楚、可解释;缺点是规则需要人工编写,难以覆盖复杂现实。

3、专家系统

专家系统是符号主义 AI 的代表。 它把专家知识写成规则,让机器根据规则做判断。 一般包含三部分:

组成作用
知识库存放专家规则
推理机根据规则推理
用户界面与用户交互

典型规则形式:

IF 条件
THEN 结论

专家系统在医学诊断等领域有过成功应用,但也面临规则维护困难、常识不足等问题。

4、SHRDLU 与 ELIZA

SHRDLU

SHRDLU 是早期综合智能体,可以在“积木世界”中理解语言、规划动作并执行任务。 它展示了早期的智能体闭环:

理解语言 → 制定计划 → 执行动作 → 更新状态

ELIZA

ELIZA 是早期聊天机器人。 它并不真正理解语言,而是通过关键词匹配和模板替换生成回复。 例如:

用户:我很焦虑
ELIZA:你为什么感到焦虑?

它说明:机器即使没有真正理解,也可以制造出“像是在理解”的效果。

5、符号主义的局限

符号主义主要有三个问题:

问题含义
知识获取瓶颈规则都要人工编写
常识不足很多常识难以完整编码
系统脆弱遇到规则外情况容易失败

因此,AI 后来逐渐转向“从数据中学习”。

6、强化学习

强化学习关注:智能体如何通过试错学习更好的行动策略。 基本流程是:

状态 → 行动 → 奖励 → 新状态 → 更新策略

例如下棋 AI 通过大量对局,逐渐学会哪些策略更容易获胜。 强化学习强调的不是某一步最优,而是长期累计奖励最大。

7、预训练模型

预训练模型的思路是

先用海量数据学习通用知识
再适配具体任务

也就是:

预训练 → 微调

大语言模型通过大规模文本预训练,学习了语言规律、事实知识和一定的推理能力。

8、LLM 驱动的现代智能体

现代智能体通常以大语言模型为核心,再结合工具、记忆和规划能力。 常见结构包括:

模块作用
LLM理解、推理、决策
记忆保存历史信息
工具调用搜索、代码、数据库等能力
规划拆解复杂任务
观察反馈根据结果调整下一步

它的工作方式可以概括为:

感知 → 规划 → 工具调用 → 观察反馈 → 继续执行

9、三大思想流派

智能体发展中有三条重要路线:

流派核心思想
符号主义智能来自规则和逻辑推理
联结主义智能来自神经网络和数据学习
行为主义智能来自与环境交互和试错

现代 LLM 智能体可以看作三者融合:

LLM 本身来自联结主义
规划和工具调用带有符号主义特点
环境交互和反馈体现行为主义思想

三、大语言模型基础

1、语言模型与Transformer架构

从N-Gram 到 RNN

统计语言模型

在深度学习兴起前,通常使用统计方法:一个句子出现的概率,等于该句子中每个词出现的条件概率的连乘。

P(S)=P(w1​,w2​,…,wm​)=P(w1​)⋅P(w2​∣w1​)⋅P(w3​∣w1​,w2​)⋯P(wm​∣w1​,…,wm−1​)P(S)=P(w1​,w2​,…,wm​)=P(w1​)⋅P(w2​∣w1​)⋅P(w3​∣w1​,w2​)⋯P(wm​∣w1​,…,wm−1​)

也被称为概率的链式法则。但w1​,⋯,wm−1w1​,⋯,wm−1 可能根本就不在语料库中,所以提出马尔可夫假设

马尔可夫假设 预测下一个token时不需要获取全部的词语,只需要看最近的几个词。 特点:简单,计算量小,但记忆太短 例如:

预测明天的天气,不需要过去一周的,只需要今天的
预测我爱北京天安门,只需要天安即可预测出门字,非马尔可夫假设也可以预测出门字,但需要左侧更多的信息

N-gram模型

看前面最近的 n-1 个词,然后统计历史上它后面最常出现什么词。 表示n-1个单词的上下文窗口大小。 例如:

bigram -> n=2 :可以看到前面1个字
trigram -> n=3 :可以看到前面2个字
4gram :可以看到前面3个字
ngram :表示可以看到前面n-1个字

特点:

  1. 数据稀疏性 (Sparsity) :如果一个词序列从未在语料库中出现,其概率估计就为 0,这显然是不合理的。虽然可以通过平滑 (Smoothing) 技术缓解,但无法根除。
  2. 泛化能力差:模型无法理解词与词之间的语义相似性。 例如:
1.语料库中只存在xx吃西红柿炒蛋,xx吃小炒牛肉等等,当输入文字为xx吃时,模型预测番茄炒蛋的概率为0
2.即使模型在语料库中见过很多次agent learns,它也无法将这个知识泛化到语义相似的词上。当计算 `robot learns` 的概率时,如果 `robot` 这个词从未出现过,或者 `robot learns` 这个组合从未出现过,模型计算出的概率也会是零。模型无法理解 `agent` 和 `robot` 在语义上的相似性。

后续的神经网络引入向量概念就解决了这个问题

神经网络语言模型与词嵌入

核心概念:

  • 构建一个语义空间:创建一个立体的向量空间,将词汇表(词典/语料库)中的每个词映射到每个点上,这个点(向量)就被称为词嵌入/词向量,相近词或关联词在该空间中距离较近
  • 学习从上下文到下一个词的映射:利用神经网络的强大拟合能力,来学习一个函数。这个函数的输入是前 n−1n−1 个词的词向量,输出是词汇表中每个词在当前上下文后出现的概率分布。 IMG-20260526162521617

在上图架构中,模型在工作过程中会不断调整每个词的位置,然后通过余弦相似度 (Cosine Similarity) 来计算两个向量夹角的余弦来衡量它们的相识度。

similarity(a⃗,b⃗)=cos⁡(θ)=a⃗⋅b⃗∣a⃗∣∣b⃗∣\mathrm{similarity}(\vec{a}, \vec{b}) = \cos(\theta) = \frac{\vec{a} \cdot \vec{b}}{|\vec{a}| |\vec{b}|}

这个公式的含义是:

  • 如果两个向量方向完全相同,夹角为0°,余弦值为1,表示完全相关。
  • 如果两个向量方向正交,夹角为90°,余弦值为0,表示毫无关系。
  • 如果两个向量方向完全相反,夹角为180°,余弦值为-1,表示完全负相关。 神经网络语言模型通过词嵌入,成功解决了 N-gram 模型的泛化能力差的问题。但仍然有一个类似 N-gram 的限制:上下文窗口是固定的,它只能考虑固定数量的前文。

循环神经网络 (RNN) 与长短时记忆网络 (LSTM)

循环神经网络 (Recurrent Neural Network, RNN)  核心思想是:为网络增加记忆能力。RNN 中有一个重要概念,叫做隐藏状态,可以把它理解成模型的“短期记忆”。 例如:

读到“我” → 形成记忆1
读到“爱” → 结合“爱”和记忆1,形成记忆2
读到“北京” → 结合“北京”和记忆2,形成记忆3
读到“天安门” → 结合“天安门”和记忆3,形成记忆4
每一步都会将当前输入 + 上一步的记忆 转为 新的记忆

与N-gram相比:它在结构上可以将前面读过的信息通过隐藏状态不断传递下去,因此相比 N-gram 和固定窗口神经网络,RNN 不再被严格限制在固定长度的上下文窗口内。不过,这种记忆是压缩在隐藏状态中的,随着序列变长,早期信息可能会逐渐衰减或被覆盖。

RNN的问题:记忆太短 为什么:

  • 梯度消失:在100层神经网络中如果想调整最后的输出词,需要反向调整前面所有的参数,每一步都有误差导致梯度趋近于0
  • 梯度爆炸:反之梯度变大

普通 RNN 虽然有“记忆”,但这个记忆并不可靠,尤其不擅长处理长句子、长文章中的远距离依赖关系。

长短时记忆网络 (Long Short-Term Memory, LSTM) 核心概念:

  • 细胞状态(Cell State):普通RNN信息存储在隐藏状态中,LSTM存储在专门传递长期记忆的通道就叫细胞状态
  • 门控机制(Gating Mechanism):
  • 遗忘门:哪些旧信息应该被忘掉?
  • 输入门:哪些新信息应该被记住?
  • 输出门:当前应该输出哪些记忆?

==RNN:让神经网络有了记忆。 LSTM:让神经网络学会管理记忆。==

Transformer架构

RNN及LSTM通过引入循环结构来处理序列数据,这在一定程度上解决了捕捉长距离依赖的问题。然而,这种循环的计算方式也带来了新的瓶颈:它必须按顺序处理数据。第 t 个时间步的计算,必须等待第 t−1 个时间步完成后才能开始。这意味着 RNN 无法进行大规模的并行计算,在处理长序列时效率低下,这极大地限制了模型规模和训练速度的提升。Transformer在2017 年由谷歌团队提出。它完全抛弃了循环结构,转而完全依赖一种名为注意力 (Attention) 的机制来捕捉序列内的依赖关系,从而实现了真正意义上的并行计算。

Encoder-Decoder 整体结构

最初的 Transformer 模型是为端到端任务机器翻译而设计的。它在宏观上遵循了一个经典的编码器-解码器 (Encoder-Decoder) 架构。

IMG-20260527095942154|400x500

  • 编码器:任务是“理解”输入的整个句子,把输入句子变成一组包含上下文信息的向量表示。
  • 解码器:任务是“生成”目标句子,根据 Encoder 的理解结果,一个词一个词生成答案。

从自注意力到多头注意力

自注意力:在同一句话内部做注意力,Transformer 会让每个词都去看其他词,每个词都会重新理解自己。 例如

我买了一个苹果。
苹果发布了新手机。

第一个“苹果”是水果。
第二个“苹果”是公司。
Transformer 可以根据上下文动态调整词的含义。

位置编码: 自注意力有个特点:它只看词之间的关系,不天然知道谁在前谁在后。所以引入位置编码,这样模型不仅知道词是什么,还知道词在哪里。由这个词是什么意思增强为这个词在某个位置上是什么意思。

注意力(Attention)在算什么: 例如:

一只猫追着老鼠,因为它很饿。
看到“它”时需要思考“它”指的是猫还是老鼠,再通过“很饿”猜测应该是“猫”饿了。

所以Transformer 和这个流程相似,每个词需要问三个问题

我现在需要找什么信息?    对应Transformer中的Query
别人能提供什么信息?    对应Transformer中的Key
别人本身携带什么内容?    对应Transformer中的Value

Attention 的本质是:根据相关性,从其他词那里取信息。

多头注意力: 一个注意力机制可能只关注一种关系,但语言里的关系很多。 例如:

主语和谓语的关系
代词和指代对象的关系
形容词和名词的关系
时间顺序关系
因果关系

所以:所以 Transformer 不只用一个 Attention,而是同时用多个 Attention。多个观察角度同时分析一句话,最后综合起来,模型就能更全面理解句子。

残差连接和 LayerNorm

  • 残差连接:不要把原来的信息全部丢掉,而是在新信息上保留一条原始信息通道。
  • 层归一化:把每一层输出调整到比较稳定的范围,避免数值太大或太小。

Transformer的优势

  • 可以并行计算
  • 擅长处理长距离依赖,词与词之间距离更远也不容易忘
  • 多头注意力同时处理多种语言关系,不仅仅是记录与预测,而是学习

Decoder-only架构

Transformer的设计哲学是“先理解,再生成”。编码器负责深入理解输入的整个句子,形成一个包含全局信息的上下文记忆,然后解码器基于这份记忆来生成翻译。但 OpenAI 在开发 GPT (Generative Pre-trained Transformer) 时,提出了一个更简单的思想:语言的核心任务,不就是预测下一个最有可能出现的词吗?

无论是回答问题、写故事还是生成代码,本质上都是在一个已有的文本序列后面,一个词一个词地添加最合理的内容。基于这个思想,GPT 做了一个大胆的简化:它完全抛弃了编码器,只保留了解码器部分。 这就是 Decoder-Only 架构的由来。

三个特点:

  • 只有解码器没有编码器:
  • 掩码自注意力(Masked Self-Attention):模型在预测第 N 个词时,只能看到第 1 到 N-1 个词,看不到后面的词。这就是掩码
  • 自回归(Auto-Regressive):文字接龙,预测出第一个词就把它加入到序列去生成第二个词,一直重复

2、与大语言模型交互

提示工程

通过设计更好的“输入提示词”,让大语言模型更稳定、更准确地完成任务。模型得到的上下文更明确,预测出来的内容就会更接近需求。 一个好的提示词通常包括:

角色:你是谁
任务:你要做什么
背景:相关信息是什么
要求:有什么限制
格式:按什么形式输出
示例:按某种格式分类

模型参数:

temperature - 温度(随机程度):越低,模型越保守;越高,模型越发散,它是基于top参数的,top提供可选范围,temperature在这个范围里面选择
top_k - 预测时固定保留前 K 个 例如:10
top_P - 预测时按累计概率动态保留,更常用 默认:1
max_tokens - 模型最多输出多少token
stop - 指定模型遇到某些字符或文本时停止输出 例如:stop = ["###"] 模型生成到###时就会停止
presence_penalty - 控制模型是否倾向于引入新话题、新词 默认:0
frequency_penalty - 控制模型是否避免生成已经生成过多次的词 默认:0
seed - 控制模型在相同参数和输入的情况下,尽量生成相同的结果(只是理想状态)

文本分词

将一段连续的文本,切成模型能处理的小单位token,现代大语言模型普遍采用子词分词 (Subword Tokenization) 算法 既不能按字切,序列越长成本越高并且三个字和一个词意义不一样;也不能按词切,模型没见过某个词时就无法理解它的意思,例如:专业术语,拼写错误,网络用词

大模型常见的分词算法:

方法常见模型思想
BPEGPT 系列常用思想之一常见组合就合并
WordPieceBERT 常用对建模有帮助就合并
UnigramT5、LLaMA 等模型常见先给很多候选,再删掉没用的

本地模型调用

"""展示 transformers 中 AutoTokenizer 和 AutoModelForCausalLM 的基础用法。"""

# ===== 启动入口说明 =====
# 这个示例需要安装 transformers 和 torch。
# 如果运行时报 ModuleNotFoundError,可以先安装:
#     uv add transformers torch
# 然后运行:
#     .venv/bin/python -m demo.transformers_auto_demo

# 本地 Hugging Face 格式模型目录。
# 这个目录和 Ollama 的模型目录不同,里面应该有 config.json、tokenizer.json、model.safetensors 等文件。
MODEL_PATH = "/Users/villow/LLM/huggingface/Qwen3-0.6B"
MAX_NEW_TOKENS = 300


def main() -> None:
    """程序入口:加载 tokenizer 和模型,然后生成一小段文本。"""

    # AutoTokenizer 负责把人类文本转换成模型能理解的 token id。
    # AutoModelForCausalLM 负责加载“因果语言模型”,也就是根据前文继续生成文本的模型。
    try:
        from transformers import AutoModelForCausalLM, AutoTokenizer
    except ModuleNotFoundError as error:
        raise SystemExit(
            "缺少 transformers 相关依赖。请先运行:uv add transformers torch"
        ) from error

    # 使用已经下载到本机的 Qwen3-0.6B 模型。
    # 因为是本地路径,所以 from_pretrained 不需要再去 Hugging Face 下载模型。
    model_name = MODEL_PATH

    # 加载 tokenizer。
    # from_pretrained 会根据 model_name 自动找到对应的 tokenizer 配置。
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    # 加载因果语言模型。
    # AutoModelForCausalLM 会根据 model_name 自动选择合适的模型类。
    # device_map="auto" 会让 transformers 自动选择可用设备;没有 GPU 时通常会使用 CPU。
    # model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")
    model = AutoModelForCausalLM.from_pretrained(model_name)

    # Qwen 这类聊天模型更推荐使用 messages 格式组织对话。
    messages = [
        {"role": "system", "content": "你是一个耐心的 Python 和 AI 学习助手。"},
        {
            "role": "user",
            "content": "用一句话解释 AutoTokenizer 和 AutoModelForCausalLM 的区别。",
        },
    ]

    # apply_chat_template 会把 messages 转成模型训练时更熟悉的聊天提示词格式。
    # add_generation_prompt=True 表示在最后加上“现在轮到助手回答”的提示。
    prompt = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
    )

    # tokenizer(...) 会把提示词字符串转换成模型输入。
    # return_tensors="pt" 表示返回 PyTorch Tensor,因为 transformers 默认常和 PyTorch 搭配使用。
    inputs = tokenizer(prompt, return_tensors="pt")

    # 把输入移动到模型所在设备上。
    # 如果模型在 CPU,这一步仍然是安全的;如果模型在 GPU/MPS,也能避免设备不一致。
    inputs = inputs.to(model.device)

    # generate 会让模型根据输入继续生成 token。
    # max_new_tokens 表示最多新生成多少个 token。
    output_ids = model.generate(
        **inputs,
        max_new_tokens=MAX_NEW_TOKENS,
        do_sample=False,
    )

    # 生成结果里会包含原始输入 token。
    # 这里把输入部分切掉,只保留模型新生成的回答。
    generated_ids = output_ids[0][inputs["input_ids"].shape[-1] :]

    # decode 会把新生成的 token id 转回人类可读文本。
    # skip_special_tokens=True 表示跳过模型内部用的特殊 token。
    output_text = tokenizer.decode(generated_ids, skip_special_tokens=True)

    # 打印模型路径和模型生成结果。
    print(f"模型路径:{model_name}")
    print(f"生成结果:{output_text}")


# 只有直接运行这个文件时,才会执行 main 函数。
if __name__ == "__main__":
    main()

输出:

Loading weights: 100%|████████████████████████████████████████████████████| 311/311 [00:00<00:00, 6059.27it/s]
模型路径:/Users/villow/LLM/huggingface/Qwen3-0.6B
生成结果:<think>
好的,用户让我用一句话解释AutoTokenizer和AutoModelForCausalLM的区别。首先,我需要明确这两个模型的区别。AutoTokenizer是用于构建模型的输入处理模块,负责将输入文本转换为模型可以处理的token序列。而AutoModelForCausalLM是模型本身,它使用这个tokenizer来生成输出。

接下来,我得考虑用户可能的背景。他们可能在学习NLP或机器学习,需要了解这两个组件的功能。用户可能没有直接问,但可能对模型结构感兴趣,或者想了解如何构建模型。需要确保解释清晰,避免术语过多,同时突出它们的核心作用。

然后,我需要检查是否有遗漏的关键点。比如,AutoTokenizer的作用是处理输入,而模型本身负责生成输出。可能用户需要知道它们的相互关系,比如tokenizer是模型的一部分,或者它们是独立的组件。需要确保一句话涵盖这两个主要区别,同时保持简洁。

最后,确保语言准确,没有错误。比如,确认AutoModelForCausalLM是用于生成输出的模型,而tokenizer是处理输入的。这样用户就能理解它们的区别,应用到实际项目中。
</think>

AutoTokenizer负责将输入文本转换为模型可处理的token序列,而AutoModelForCausalLM是构建模型的主体,通过该tokenizer生成输出。

3、缩放法则与幻觉

缩放法则

模型性能会随着模型参数量、训练数据量、训练计算量的增加,以相对平滑、可预测的方式提升。经典研究发现,语言模型的交叉熵损失会随模型规模、数据规模、计算量呈现近似幂律下降,而且这种趋势可以跨越多个数量级

IMG-20260527160545122

缩放法则就是大模型工程里的“投入—产出曲线”:更多参数、更多数据、更多算力通常会带来可预测的性能提升,但最佳做法不是盲目堆大,而是按计算预算平衡模型规模和数据规模

幻觉

模型生成了看起来很合理、语气很自信,但实际上不正确、无依据或与事实不一致的内容

**为什么会产生幻觉?**
  1. 模型训练目标不是“保证真实”,而是“生成符合语言模式的文本”。如果某个错误答案在语言形式上很像正确答案,模型也可能生成它
  2. 模型可能缺少最新信息、专有信息或上下文信息。信息不够时,它有时会用模式补全,而不是主动承认不知道
  3. 用户问题本身可能有误导性。例如问“某某不存在的概念有什么优点”,模型可能顺着问题继续解释
  4. 模型对自身不确定性的表达不稳定。有时它并不知道自己不知道

幻觉和普通错误有什么区别? 普通错误可能是算错、理解错、信息过期 幻觉更强调:模型生成了没有依据的内容,并且表达得像真的一样。它的危险性在于“可信的语气”会掩盖“不可信的事实”

怎么减少幻觉? 比较有效的方法包括:

1. 让模型基于资料回答 - 例如上传文档、提供链接、给出数据库内容,让模型只根据材料作答
2. 要求引用来源 - 尤其是法律、医疗、金融、科研、新闻类问题,要让模型说明依据
3. 允许模型说不知道 - 提示中可以写:“如果没有依据,请直接说不知道,不要猜”
4. 使用检索增强生成,RAG - 先从可信知识库中检索相关资料,再让模型基于资料生成答案
5. 把任务拆小 - 复杂推理任务中,分步骤验证比一次性问大问题更可靠
6. 对高风险内容人工复核 - 合同、诊断、投资、学术引用、代码安全等场景不能只依赖模型输出

四、 智能体范式

1、ReAct (Reasoning + Acting)

含义:推理 + 行动,它是一种让大模型智能体“边思考、边行动”的工作方式

和普通的问答方式有什么区别:

  • 普通大模型回答:用户问问题 → 模型直接回答
  • ReAct回答:用户问问题→ 模型先思考→ 决定是否需要工具→ 调用工具→ 读取工具结果→ 再思考→ 最后回答 所以ReAct更适合搜索资料,调用api,操作数据库,执行代码,使用浏览器

编码实现

"""使用 LangChain 演示 ReAct 思想的 Agent 示例。

ReAct = Reasoning + Acting:
模型先根据问题进行推理,然后决定是否调用工具,再根据工具结果继续推理,最后给出答案。
"""

# ===== 启动入口说明 =====
# 在 IDE 里可以直接点击当前文件的 Run / 启动按钮。
# 在终端里可以运行:
#     .venv/bin/python -m demo.react_agent_demo

# ast 用来安全解析简单数学表达式,避免直接 eval 任意代码。
import ast

# importlib.util 用来按文件路径加载同目录里的 local_settings.py。
import importlib.util

# operator 提供安全的数学运算函数,例如加法、乘法。
import operator

# Path 用来定位当前文件所在目录。
from pathlib import Path

# create_agent 是 LangChain 1.x 中创建 Agent 的推荐入口。
from langchain.agents import create_agent

# ChatOpenAI 是 LangChain 提供的 OpenAI-compatible 聊天模型封装。
from langchain_openai import ChatOpenAI

# tool 装饰器可以把普通 Python 函数变成 Agent 可调用的工具。
from langchain_core.tools import tool


# 默认模型名:如果 local_settings.py 没配置 OPENAI_MODEL,就使用它。
DEFAULT_MODEL = "gpt-5.5"

# 默认请求地址:空字符串表示使用 OpenAI SDK 默认地址。
DEFAULT_BASE_URL = ""

# 默认问题:这个问题需要同时用到单词长度工具和计算器工具。
DEFAULT_QUESTION = "apple 这个单词的长度加上 12 * 3 等于多少?"


def main() -> None:
    """程序入口:创建模型、创建工具、运行 ReAct Agent。"""

    print("=== ReAct Agent Demo 启动 ===")

    # 加载项目内配置,避免污染其他项目的全局环境变量。
    print("阶段 1:读取 demo/local_settings.py 项目内配置...")
    settings = load_local_settings()

    # 创建 OpenAI-compatible 模型对象。
    print("阶段 2:根据配置创建 OpenAI-compatible 聊天模型...")
    llm = create_chat_model(settings)

    # 准备 Agent 可以调用的工具列表。
    print("阶段 3:注册 Agent 可用工具:word_length、calculator...")
    tools = [word_length, calculator]

    # system_prompt 用来告诉模型:你可以使用工具,并且要用 ReAct 风格解决问题。
    system_prompt = (
        "你是一个 ReAct Agent。遇到需要计算或查询的问题时,"
        "先判断应该调用哪个工具,再根据工具返回结果回答。"
    )

    # create_agent 会把模型和工具组装成一个 Agent。
    # debug=True 会在终端打印 Agent 的中间步骤,方便学习 ReAct 的执行过程。
    print("阶段 4:创建 LangChain Agent,并开启 debug 日志...")
    agent = create_agent(
        model=llm,
        tools=tools,
        system_prompt=system_prompt,
        debug=True,
    )

    # 用户问题。你可以改这个问题,观察 Agent 是否会选择不同工具。
    question = get_setting(settings, "REACT_DEMO_QUESTION", DEFAULT_QUESTION)
    print(f"阶段 5:准备用户问题:{question}")
    print("阶段 6:开始运行 Agent。下面会看到 LangChain debug 输出,包括模型消息和工具调用。")

    # LangChain Agent 使用 messages 格式输入,和聊天模型的消息结构一致。
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": question,
                }
            ]
        }
    )

    # Agent 的最终消息通常在 messages 列表最后一个位置。
    print("阶段 7:Agent 运行结束,提取最后一条模型消息作为最终回答。")
    final_message = result["messages"][-1]

    # content 是模型最终回答。
    print("\n最终回答:")
    print(get_message_text(final_message.content))


def load_local_settings():
    """加载当前目录下的 local_settings.py 配置文件。"""

    # __file__ 是当前 react_agent_demo.py 的文件路径。
    # with_name 会把文件名替换成 local_settings.py,从而定位同目录配置文件。
    settings_path = Path(__file__).with_name("local_settings.py")

    # 如果配置文件不存在,直接提示用户创建它。
    if not settings_path.exists():
        raise SystemExit("Missing demo/local_settings.py. 请先创建项目内配置文件。")

    # spec_from_file_location 可以从指定路径创建模块加载说明。
    spec = importlib.util.spec_from_file_location("local_settings", settings_path)
    if spec is None or spec.loader is None:
        raise SystemExit("Cannot load demo/local_settings.py.")

    # 根据加载说明创建模块对象。
    settings = importlib.util.module_from_spec(spec)

    # 执行配置文件,把里面的变量加载到 settings 对象上。
    spec.loader.exec_module(settings)
    return settings


def get_setting(settings, name: str, default: str) -> str:
    """从配置对象读取字符串配置;没有配置时使用默认值。"""

    # getattr 会按变量名读取配置,例如读取 settings.OPENAI_MODEL。
    value = getattr(settings, name, default)

    # 空字符串表示没有配置,所以回退到默认值。
    return value or default


def get_bool_setting(settings, name: str, default: bool) -> bool:
    """从配置对象读取布尔值配置;没有配置时使用默认值。"""

    # getattr 会按变量名读取配置,例如读取 settings.OPENAI_USE_RESPONSES_API。
    value = getattr(settings, name, default)

    # 如果配置本身就是 True 或 False,直接返回。
    if isinstance(value, bool):
        return value

    # 如果配置写成字符串,也支持常见写法,例如 "true"、"false"。
    if isinstance(value, str):
        return value.strip().lower() in {"1", "true", "yes", "on"}

    # 其他类型不做复杂处理,回退到默认值。
    return default


def create_chat_model(settings) -> ChatOpenAI:
    """根据 local_settings.py 创建 OpenAI-compatible 聊天模型。"""

    # 从项目内配置读取模型参数。
    api_key = get_setting(settings, "OPENAI_API_KEY", "")
    base_url = get_setting(settings, "OPENAI_BASE_URL", DEFAULT_BASE_URL)
    model = get_setting(settings, "OPENAI_MODEL", DEFAULT_MODEL)
    use_responses_api = get_bool_setting(settings, "OPENAI_USE_RESPONSES_API", False)

    # 没有 API Key 时,直接给出明确提示。
    if not api_key:
        raise SystemExit("Missing OPENAI_API_KEY. 请先在 demo/local_settings.py 中配置。")

    # 准备 ChatOpenAI 初始化参数。
    model_kwargs = {
        "model": model,
        "api_key": api_key,
        "use_responses_api": use_responses_api,
    }

    # 如果配置了 base_url,请求会走这个 OpenAI-compatible 地址。
    if base_url:
        model_kwargs["base_url"] = base_url

    # 返回 LangChain 聊天模型对象。
    return ChatOpenAI(**model_kwargs)


def get_message_text(content) -> str:
    """把模型返回内容统一转换成普通文本。"""

    # 有些接口会直接返回字符串,这种情况直接返回。
    if isinstance(content, str):
        return content

    # Responses API 常见返回格式是列表,里面每一项是一个字典。
    if isinstance(content, list):
        texts = []

        # 遍历列表,提取 type 为 text 的文本块。
        for item in content:
            if isinstance(item, dict) and item.get("type") == "text":
                texts.append(item.get("text", ""))

        # 用换行拼接多个文本块。
        return "\n".join(text for text in texts if text)

    # 如果遇到其他格式,转成字符串,避免程序报错。
    return str(content)


@tool
def word_length(word: str) -> int:
    """计算一个英文单词的字符数量。"""

    # strip 会去掉单词两侧的空白字符。
    return len(word.strip())


@tool
def calculator(expression: str) -> float:
    """计算只包含加减乘除和括号的数学表达式。"""

    # ast.parse 会把表达式字符串解析成 Python 语法树。
    tree = ast.parse(expression, mode="eval")

    # 递归计算语法树。
    return evaluate_math_node(tree.body)


def evaluate_math_node(node) -> float:
    """递归计算安全的数学表达式节点。"""

    # 支持数字常量,例如 12、3.5。
    if isinstance(node, ast.Constant) and isinstance(node.value, int | float):
        return node.value

    # 支持一元负号,例如 -3。
    if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
        return -evaluate_math_node(node.operand)

    # 支持二元运算,例如 12 * 3。
    if isinstance(node, ast.BinOp):
        left = evaluate_math_node(node.left)
        right = evaluate_math_node(node.right)

        # 只允许常见四则运算,避免执行任意 Python 代码。
        operators = {
            ast.Add: operator.add,
            ast.Sub: operator.sub,
            ast.Mult: operator.mul,
            ast.Div: operator.truediv,
        }

        # 根据语法树里的运算符类型,找到对应的安全计算函数。
        operation = operators.get(type(node.op))
        if operation is None:
            raise ValueError("只支持加、减、乘、除。")

        # 返回计算结果。
        return operation(left, right)

    # 其他节点一律拒绝,例如函数调用、变量名、属性访问等。
    raise ValueError("表达式不安全或不支持。")


# 只有直接运行这个文件时才执行 main;被其他文件 import 时不会自动执行。
if __name__ == "__main__":
    main()

输出:

=== ReAct Agent Demo 启动 ===
阶段 1:读取 demo/local_settings.py 项目内配置...
阶段 2:根据配置创建 OpenAI-compatible 聊天模型...
阶段 3:注册 Agent 可用工具:word_length、calculator...
阶段 4:创建 LangChain Agent,并开启 debug 日志...
阶段 5:准备用户问题:apple 这个单词的长度加上 12 * 3 等于多少?
阶段 6:开始运行 Agent。下面会看到 LangChain debug 输出,包括模型消息和工具调用。
[values] {'messages': [HumanMessage(content='apple 这个单词的长度加上 12 * 3 等于多少?', additional_kwargs={}, response_metadata={}, id='e0540787-b59e-40b9-98ce-6e25a76791a7')]}
[updates] {'model': {'messages': [AIMessage(content=[{'arguments': '{"word":"apple"}', 'call_id': 'call_cnKHTRAsdboqXtx4mR6m40Ka', 'name': 'word_length', 'type': 'function_call'}, {'arguments': '{"expression":"12*3"}', 'call_id': 'call_iA63aK83UISglvgJLqR5NuPK', 'name': 'calculator', 'type': 'function_call'}], additional_kwargs={}, response_metadata={'id': 'resp_00710547bebdb972016a16af3cdf308199b7e879342b8c2a53', 'created_at': 1779871548.0, 'metadata': {}, 'model': 'gpt-5.5', 'object': 'response', 'service_tier': 'default', 'status': 'completed', 'model_provider': 'openai', 'model_name': 'gpt-5.5'}, id='resp_00710547bebdb972016a16af3cdf308199b7e879342b8c2a53', tool_calls=[{'name': 'word_length', 'args': {'word': 'apple'}, 'id': 'call_cnKHTRAsdboqXtx4mR6m40Ka', 'type': 'tool_call'}, {'name': 'calculator', 'args': {'expression': '12*3'}, 'id': 'call_iA63aK83UISglvgJLqR5NuPK', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 133, 'output_tokens': 87, 'total_tokens': 220, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 35}})]}}
xxx...
阶段 7:Agent 运行结束,提取最后一条模型消息作为最终回答。

最终回答:
apple 的长度是 5,12 × 3 = 36,所以:

5 + 36 = **41**

ReAct 的主要特点

ReAct 的核心是 边思考、边行动。

  • 可解释性强:通过 Thought 可以看到智能体每一步为什么这么做,方便理解和调试
  • 能动态调整:它不是一次性把计划定死,而是根据工具返回的 Observation,不断修正下一步行动
  • 适合工具协同:LLM 负责推理和规划,工具负责搜索、计算、查询等具体任务,两者配合完成复杂问题

局限性

  • 效率较低:因为它需要多轮“思考—行动—观察”,所以耗时和成本都会增加
  • 提示词不稳定:ReAct 很依赖提示词模板,提示词稍微变化,模型行为可能就不一样
  • 可能陷入局部最优:它是一步步决策的,可能只关注当前结果,缺少全局规划,甚至反复尝试同一类错误路径

2、Plan-and-Solve

含义:计划和解决,先把问题拆成步骤,再按照步骤一步步解决

编码实现

|fold
"""使用 LangChain 演示 Plan-and-Solve 思想。

Plan-and-Solve = 先规划,再求解:
模型先把复杂问题拆成清晰步骤,然后再按照计划逐步解决问题。
"""

# ===== 启动入口说明 =====
# 在 IDE 里可以直接点击当前文件的 Run / 启动按钮。
# 在终端里可以运行:
#     .venv/bin/python -m demo.plan_and_solve_demo

# importlib.util 用来按文件路径加载同目录里的 local_settings.py。
import importlib.util

# Path 用来定位当前文件所在目录。
from pathlib import Path

# ChatOpenAI 是 LangChain 提供的 OpenAI-compatible 聊天模型封装。
from langchain_openai import ChatOpenAI


# 默认模型名:如果 local_settings.py 没配置 OPENAI_MODEL,就使用它。
DEFAULT_MODEL = "gpt-5.5"

# 默认请求地址:空字符串表示使用 OpenAI SDK 默认地址。
DEFAULT_BASE_URL = ""

# 默认问题:刻意换成旅行预算问题,和 ReAct demo 的算术问题区分开。
DEFAULT_QUESTION = (
    "我计划周末去上海玩 2 天。高铁往返 560 元,酒店每晚 380 元,"
    "每天餐饮 150 元,景点门票总共 220 元。请帮我计算总预算,"
    "并判断 1800 元预算是否够。"
)


def main() -> None:
    """程序入口:先让模型制定计划,再让模型按计划解决问题。"""

    print("=== Plan-and-Solve Demo 启动 ===")

    # 加载项目内配置,避免污染其他项目的全局环境变量。
    print("阶段 1:读取 demo/local_settings.py 项目内配置...")
    settings = load_local_settings()

    # 创建 OpenAI-compatible 模型对象。
    print("阶段 2:根据配置创建 OpenAI-compatible 聊天模型...")
    llm = create_chat_model(settings)

    # 准备用户问题。
    question = get_setting(settings, "PLAN_AND_SOLVE_DEMO_QUESTION", DEFAULT_QUESTION)
    print(f"阶段 3:准备用户问题:{question}")

    # Plan 阶段:先让模型只制定计划,不直接求最终答案。
    print("阶段 4:Plan 阶段,让模型先拆解解决步骤...")
    plan = make_plan(llm, question)
    print("\n模型生成的计划:")
    print(plan)

    # Solve 阶段:把问题和计划一起交给模型,让它按计划求解。
    print("\n阶段 5:Solve 阶段,让模型按照计划逐步解决问题...")
    answer = solve_with_plan(llm, question, plan)

    # 打印最终回答。
    print("\n最终回答:")
    print(answer)


def load_local_settings():
    """加载当前目录下的 local_settings.py 配置文件。"""

    # __file__ 是当前 plan_and_solve_demo.py 的文件路径。
    # with_name 会把文件名替换成 local_settings.py,从而定位同目录配置文件。
    settings_path = Path(__file__).with_name("local_settings.py")

    # 如果配置文件不存在,直接提示用户创建它。
    if not settings_path.exists():
        raise SystemExit("Missing demo/local_settings.py. 请先创建项目内配置文件。")

    # spec_from_file_location 可以从指定路径创建模块加载说明。
    spec = importlib.util.spec_from_file_location("local_settings", settings_path)
    if spec is None or spec.loader is None:
        raise SystemExit("Cannot load demo/local_settings.py.")

    # 根据加载说明创建模块对象。
    settings = importlib.util.module_from_spec(spec)

    # 执行配置文件,把里面的变量加载到 settings 对象上。
    spec.loader.exec_module(settings)
    return settings


def get_setting(settings, name: str, default: str) -> str:
    """从配置对象读取字符串配置;没有配置时使用默认值。"""

    # getattr 会按变量名读取配置,例如读取 settings.OPENAI_MODEL。
    value = getattr(settings, name, default)

    # 空字符串表示没有配置,所以回退到默认值。
    return value or default


def get_bool_setting(settings, name: str, default: bool) -> bool:
    """从配置对象读取布尔值配置;没有配置时使用默认值。"""

    # getattr 会按变量名读取配置,例如读取 settings.OPENAI_USE_RESPONSES_API。
    value = getattr(settings, name, default)

    # 如果配置本身就是 True 或 False,直接返回。
    if isinstance(value, bool):
        return value

    # 如果配置写成字符串,也支持常见写法,例如 "true"、"false"。
    if isinstance(value, str):
        return value.strip().lower() in {"1", "true", "yes", "on"}

    # 其他类型不做复杂处理,回退到默认值。
    return default


def create_chat_model(settings) -> ChatOpenAI:
    """根据 local_settings.py 创建 OpenAI-compatible 聊天模型。"""

    # 从项目内配置读取模型参数。
    api_key = get_setting(settings, "OPENAI_API_KEY", "")
    base_url = get_setting(settings, "OPENAI_BASE_URL", DEFAULT_BASE_URL)
    model = get_setting(settings, "OPENAI_MODEL", DEFAULT_MODEL)
    use_responses_api = get_bool_setting(settings, "OPENAI_USE_RESPONSES_API", False)

    # 没有 API Key 时,直接给出明确提示。
    if not api_key:
        raise SystemExit("Missing OPENAI_API_KEY. 请先在 demo/local_settings.py 中配置。")

    # 准备 ChatOpenAI 初始化参数。
    model_kwargs = {
        "model": model,
        "api_key": api_key,
        "use_responses_api": use_responses_api,
    }

    # 如果配置了 base_url,请求会走这个 OpenAI-compatible 地址。
    if base_url:
        model_kwargs["base_url"] = base_url

    # 返回 LangChain 聊天模型对象。
    return ChatOpenAI(**model_kwargs)


def make_plan(llm: ChatOpenAI, question: str) -> str:
    """Plan 阶段:只生成解题计划。"""

    # 明确告诉模型:这里只要计划,不要最终答案。
    prompt = f"""
你正在执行 Plan-and-Solve 的 Plan 阶段。
请只为下面的问题制定解题计划,不要直接计算最终答案。

要求:
1. 用中文输出。
2. 使用编号列表。
3. 每一步都要具体说明要计算或判断什么。

问题:
{question}
"""

    # 调用模型生成计划。
    response = llm.invoke(prompt)

    # 把不同接口格式的返回内容统一转成普通文本。
    return get_message_text(response.content)


def solve_with_plan(llm: ChatOpenAI, question: str, plan: str) -> str:
    """Solve 阶段:根据计划逐步解决问题。"""

    # 把原问题和 Plan 阶段生成的计划一起给模型。
    prompt = f"""
你正在执行 Plan-and-Solve 的 Solve 阶段。
请严格按照给定计划一步步解决问题,并给出最终结论。

原问题:
{question}

计划:
{plan}

要求:
1. 展示关键计算过程。
2. 最后明确回答预算是否足够。
3. 用中文回答。
"""

    # 调用模型生成解答。
    response = llm.invoke(prompt)

    # 把不同接口格式的返回内容统一转成普通文本。
    return get_message_text(response.content)


def get_message_text(content) -> str:
    """把模型返回内容统一转换成普通文本。"""

    # 有些接口会直接返回字符串,这种情况直接返回。
    if isinstance(content, str):
        return content

    # Responses API 常见返回格式是列表,里面每一项是一个字典。
    if isinstance(content, list):
        texts = []

        # 遍历列表,提取 type 为 text 的文本块。
        for item in content:
            if isinstance(item, dict) and item.get("type") == "text":
                texts.append(item.get("text", ""))

        # 用换行拼接多个文本块。
        return "\n".join(text for text in texts if text)

    # 如果遇到其他格式,转成字符串,避免程序报错。
    return str(content)


# 只有直接运行这个文件时才执行 main;被其他文件 import 时不会自动执行。
if __name__ == "__main__":
    main()

输出:

=== Plan-and-Solve Demo 启动 ===
阶段 1:读取 1start/local_settings.py 项目内配置...
阶段 2:根据配置创建 OpenAI-compatible 聊天模型...
阶段 3:准备用户问题:我计划周末去上海玩 2 天。高铁往返 560 元,酒店每晚 380 元,每天餐饮 150 元,景点门票总共 220 元。请帮我计算总预算,并判断 1800 元预算是否够。
阶段 4:Plan 阶段,让模型先拆解解决步骤...

模型生成的计划:
1. 计算交通费用:确认高铁往返费用为 **560 元**,作为总交通预算。

2. 计算酒店费用:判断 2 天行程通常需要住几晚;如果是周末玩 2 天,一般按 **1 晚酒店** 计算,再用“每晚 380 元 × 住宿晚数”得到酒店总费用。

3. 计算餐饮费用:用“每天餐饮 150 元 × 2 天”计算 2 天的餐饮总费用。

4. 计算景点门票费用:将题目给出的景点门票总费用 **220 元**计入预算。

5. 计算总预算:把交通费用、酒店费用、餐饮费用和景点门票费用相加,得到本次上海 2 天游玩的总预算。

6. 判断 1800 元是否够:将计算出的总预算与 **1800 元**进行比较;如果总预算小于或等于 1800 元,则预算够;如果总预算大于 1800 元,则预算不够。

7. 如有剩余预算:用“1800 元 − 总预算”计算剩余金额;如果不够,则用“总预算 − 1800 元”计算超出金额。

阶段 5:Solve 阶段,让模型按照计划逐步解决问题...

最终回答:
按计划计算如下:

1. **交通费用**
   高铁往返费用:**560 元**

2. **酒店费用**
   周末玩 2 天,一般按住 **1 晚**计算:
   380 × 1 = **380 元**

3. **餐饮费用**
   每天餐饮 150 元,2 天:
   150 × 2 = **300 元**

4. **景点门票费用**
   景点门票总共:**220 元**

5. **总预算计算**
   总预算 = 交通费 + 酒店费 + 餐饮费 + 景点门票
   = 560 + 380 + 300 + 220
   = **1460 元**

6. **判断 1800 元是否够**
   1800 元 − 1460 元 = **340 元**

最终结论:**1800 元预算足够**,预计总花费为 **1460 元**,还剩 **340 元**。

Plan-and-Solve的主要特点

  • 结构清晰:先列计划,回答不会太偏差太大
  • 减少遗漏步骤:先把任务拆开,不容易漏掉关键环节
  • 适合复杂任务:比如分析、推理、写方案、解决数学题、多步骤决策等

局限性

  • 计划可能一开始就错了:如果第一步计划不合理,后面执行也会跟着错
  • 缺少动态纠错能力:不会像ReAct一样根据工具返回结果进行调整

3、Reflection

含义:反思,先完成一次任务,然后回头检查自己的结果,发现问题,再进行修改和优化 核心工作流程可以概括为一个简洁的三步循环:执行 -> 反思 -> 优化

  1. **执行 (Execution)**:首先,智能体使用我们熟悉的方法(如 ReAct 或 Plan-and-Solve)尝试完成任务,生成一个初步的解决方案或行动轨迹。这可以看作是“初稿”。
  2. **反思 (Reflection)**:接着,智能体进入反思阶段。它会调用一个独立的、或者带有特殊提示词的大语言模型实例,来扮演一个“评审员”的角色。这个“评审员”会审视第一步生成的“初稿”,并从多个维度进行评估,例如:
  3. **优化 (Refinement)**:最后,智能体将“初稿”和“反馈”作为新的上下文,再次调用大语言模型,要求它根据反馈内容对初稿进行修正,生成一个更完善的“修订稿”。

编码实现

"""使用 LangChain 演示 Reflection 反思模式。

Reflection = 先生成答案,再检查答案,再根据检查结果改进答案。
这个模式适合写作、解释、总结、代码审查等需要提高质量的任务。
"""

# ===== 启动入口说明 =====
# 在 IDE 里可以直接点击当前文件的 Run / 启动按钮。
# 在终端里可以运行:
#     .venv/bin/python -m demo.reflection_demo

# importlib.util 用来按文件路径加载同目录里的 local_settings.py。
import importlib.util

# Path 用来定位当前文件所在目录。
from pathlib import Path

# ChatOpenAI 是 LangChain 提供的 OpenAI-compatible 聊天模型封装。
from langchain_openai import ChatOpenAI


# 默认模型名:如果 local_settings.py 没配置 OPENAI_MODEL,就使用它。
DEFAULT_MODEL = "gpt-5.5"

# 默认请求地址:空字符串表示使用 OpenAI SDK 默认地址。
DEFAULT_BASE_URL = ""

# 默认问题:选择一个适合初学者学习的 Python 问题,方便观察反思前后的变化。
DEFAULT_QUESTION = "请解释 Python 中列表和元组的区别,并给一个适合初学者的例子。"


def main() -> None:
    """程序入口:生成初稿、反思初稿、根据反思改进答案。"""

    print("=== Reflection Demo 启动 ===")

    # 加载项目内配置,避免污染其他项目的全局环境变量。
    print("阶段 1:读取 demo/local_settings.py 项目内配置...")
    settings = load_local_settings()

    # 创建 OpenAI-compatible 模型对象。
    print("阶段 2:根据配置创建 OpenAI-compatible 聊天模型...")
    llm = create_chat_model(settings)

    # 准备用户问题。
    question = get_setting(settings, "REFLECTION_DEMO_QUESTION", DEFAULT_QUESTION)
    print(f"阶段 3:准备用户问题:{question}")

    # Draft 阶段:先让模型直接回答,得到第一版答案。
    print("阶段 4:Draft 初稿阶段,先让模型直接回答问题...")
    draft = create_draft(llm, question)
    print("\n初稿答案:")
    print(draft)

    # Reflect 阶段:让模型检查初稿有哪些问题。
    print("\n阶段 5:Reflect 反思阶段,检查初稿是否有遗漏、跳跃或表达不清...")
    reflection = reflect_on_draft(llm, question, draft)
    print("\n反思意见:")
    print(reflection)

    # Revise 阶段:让模型根据反思意见改进答案。
    print("\n阶段 6:Revise 改进阶段,根据反思意见生成最终答案...")
    revised_answer = revise_answer(llm, question, draft, reflection)

    # 打印最终答案。
    print("\n最终答案:")
    print(revised_answer)


def load_local_settings():
    """加载当前目录下的 local_settings.py 配置文件。"""

    # __file__ 是当前 reflection_demo.py 的文件路径。
    # with_name 会把文件名替换成 local_settings.py,从而定位同目录配置文件。
    settings_path = Path(__file__).with_name("local_settings.py")

    # 如果配置文件不存在,直接提示用户创建它。
    if not settings_path.exists():
        raise SystemExit("Missing demo/local_settings.py. 请先创建项目内配置文件。")

    # spec_from_file_location 可以从指定路径创建模块加载说明。
    spec = importlib.util.spec_from_file_location("local_settings", settings_path)
    if spec is None or spec.loader is None:
        raise SystemExit("Cannot load 1start/local_settings.py.")

    # 根据加载说明创建模块对象。
    settings = importlib.util.module_from_spec(spec)

    # 执行配置文件,把里面的变量加载到 settings 对象上。
    spec.loader.exec_module(settings)
    return settings


def get_setting(settings, name: str, default: str) -> str:
    """从配置对象读取字符串配置;没有配置时使用默认值。"""

    # getattr 会按变量名读取配置,例如读取 settings.OPENAI_MODEL。
    value = getattr(settings, name, default)

    # 空字符串表示没有配置,所以回退到默认值。
    return value or default


def get_bool_setting(settings, name: str, default: bool) -> bool:
    """从配置对象读取布尔值配置;没有配置时使用默认值。"""

    # getattr 会按变量名读取配置,例如读取 settings.OPENAI_USE_RESPONSES_API。
    value = getattr(settings, name, default)

    # 如果配置本身就是 True 或 False,直接返回。
    if isinstance(value, bool):
        return value

    # 如果配置写成字符串,也支持常见写法,例如 "true"、"false"。
    if isinstance(value, str):
        return value.strip().lower() in {"1", "true", "yes", "on"}

    # 其他类型不做复杂处理,回退到默认值。
    return default


def create_chat_model(settings) -> ChatOpenAI:
    """根据 local_settings.py 创建 OpenAI-compatible 聊天模型。"""

    # 从项目内配置读取模型参数。
    api_key = get_setting(settings, "OPENAI_API_KEY", "")
    base_url = get_setting(settings, "OPENAI_BASE_URL", DEFAULT_BASE_URL)
    model = get_setting(settings, "OPENAI_MODEL", DEFAULT_MODEL)
    use_responses_api = get_bool_setting(settings, "OPENAI_USE_RESPONSES_API", False)

    # 没有 API Key 时,直接给出明确提示。
    if not api_key:
        raise SystemExit("Missing OPENAI_API_KEY. 请先在 demo/local_settings.py 中配置。")

    # 准备 ChatOpenAI 初始化参数。
    model_kwargs = {
        "model": model,
        "api_key": api_key,
        "use_responses_api": use_responses_api,
    }

    # 如果配置了 base_url,请求会走这个 OpenAI-compatible 地址。
    if base_url:
        model_kwargs["base_url"] = base_url

    # 返回 LangChain 聊天模型对象。
    return ChatOpenAI(**model_kwargs)


def create_draft(llm: ChatOpenAI, question: str) -> str:
    """Draft 阶段:生成第一版答案。"""

    # 这里故意只要求“直接回答”,让初稿可能存在可改进空间。
    prompt = f"""
请直接回答下面的问题,面向 Python 初学者,语言尽量简洁。

问题:
{question}
"""

    # 调用模型生成初稿。
    response = llm.invoke(prompt)

    # 把不同接口格式的返回内容统一转成普通文本。
    return get_message_text(response.content)


def reflect_on_draft(llm: ChatOpenAI, question: str, draft: str) -> str:
    """Reflect 阶段:反思初稿有什么可以改进。"""

    # 反思阶段不要求重新回答,而是要求模型扮演审稿人。
    prompt = f"""
你正在执行 Reflection 模式的 Reflect 阶段。
请检查下面的初稿答案,指出它可以改进的地方。

原问题:
{question}

初稿答案:
{draft}

请从下面角度反思:
1. 是否遗漏关键信息?
2. 是否有逻辑跳跃?
3. 是否有表达不清的地方?
4. 是否需要补充更适合初学者的例子?
5. 是否存在事实错误?

只输出反思意见,不要直接重写最终答案。
"""

    # 调用模型生成反思意见。
    response = llm.invoke(prompt)

    # 把不同接口格式的返回内容统一转成普通文本。
    return get_message_text(response.content)


def revise_answer(llm: ChatOpenAI, question: str, draft: str, reflection: str) -> str:
    """Revise 阶段:根据反思意见改进答案。"""

    # 改进阶段把原问题、初稿和反思意见都交给模型。
    prompt = f"""
你正在执行 Reflection 模式的 Revise 阶段。
请根据反思意见,重写并改进答案。

原问题:
{question}

初稿答案:
{draft}

反思意见:
{reflection}

要求:
1. 面向 Python 初学者。
2. 解释要清楚但不要啰嗦。
3. 必须包含一个简单代码例子。
4. 最后用一句话总结列表和元组的核心区别。
"""

    # 调用模型生成最终答案。
    response = llm.invoke(prompt)

    # 把不同接口格式的返回内容统一转成普通文本。
    return get_message_text(response.content)


def get_message_text(content) -> str:
    """把模型返回内容统一转换成普通文本。"""

    # 有些接口会直接返回字符串,这种情况直接返回。
    if isinstance(content, str):
        return content

    # Responses API 常见返回格式是列表,里面每一项是一个字典。
    if isinstance(content, list):
        texts = []

        # 遍历列表,提取 type 为 text 的文本块。
        for item in content:
            if isinstance(item, dict) and item.get("type") == "text":
                texts.append(item.get("text", ""))

        # 用换行拼接多个文本块。
        return "\n".join(text for text in texts if text)

    # 如果遇到其他格式,转成字符串,避免程序报错。
    return str(content)


# 只有直接运行这个文件时才执行 main;被其他文件 import 时不会自动执行。
if __name__ == "__main__":
    main()

输出:

=== Reflection Demo 启动 ===
阶段 1:读取 demo/local_settings.py 项目内配置...
阶段 2:根据配置创建 OpenAI-compatible 聊天模型...
阶段 3:准备用户问题:请解释 Python 中列表和元组的区别,并给一个适合初学者的例子。
阶段 4:Draft 初稿阶段,先让模型直接回答问题...

初稿答案:
Python 中**列表(list)**和**元组(tuple)**都可以用来存放多个数据。

主要区别:


1. **列表可以修改**
   - 用 `[]`
   - 可以添加、删除、修改元素

2. **元组不能修改**
   - 用 `()`
   - 创建后内容不能改变

例子:

```python
# 列表
fruits = ["苹果", "香蕉", "橙子"]
fruits[0] = "西瓜"   # 可以修改
fruits.append("葡萄") # 可以添加

print(fruits)
```

输出:

```python
['西瓜', '香蕉', '橙子', '葡萄']
```

元组例子:

```python
# 元组
colors = ("红色", "绿色", "蓝色")
print(colors[0])
```

如果这样写会报错:

```python
colors[0] = "黄色"  # 元组不能修改
```

简单记忆:

- **列表 list**:适合存放以后可能会改变的数据
- **元组 tuple**:适合存放固定不变的数据

阶段 5:Reflect 反思阶段,检查初稿是否有遗漏、跳跃或表达不清...

反思意见:
反思意见如下:

1. **关键信息基本完整,但可以补充一点**
   - 已经说明了列表和元组的核心区别:列表可修改,元组不可修改。
   - 可以补充说明它们的共同点,例如:
     - 都可以存放多个数据;
     - 都可以通过下标访问元素;
     - 都可以遍历;
     - 都可以存放不同类型的数据。
   - 这样初学者能更清楚地理解“它们相似在哪里,区别在哪里”。

2. **“元组不能修改”可以表达得更准确**
   - 初稿说“元组不能修改”对初学者来说基本正确。
   - 但如果更严谨,可以说明:元组本身不可变,不能增加、删除、替换元素。
   - 不过不建议在初学者答案中过早展开“元组里包含可变对象时的特殊情况”,否则可能增加理解负担。

3. **例子清晰,但列表和元组例子不够对称**
   - 列表例子展示了修改和添加,很直观。
   - 元组例子只展示了访问和错误修改,略微简单。
   - 可以让两个例子使用相似场景,比如:
     - 列表表示“购物车”,因为商品可能增加或删除;
     - 元组表示“出生日期”或“坐标”,因为通常不会改变。
   - 这样更符合“适合初学者”的要求,也更容易理解使用场景。

4. **可以补充一个生活化类比**
   - 初学者可能对“可变”和“不可变”不敏感。
   - 可以用简单类比帮助理解,例如:
     - 列表像购物清单,可以随时加减;
     - 元组像身份证上的出生日期,确定后一般不改。
   - 这会让答案更友好。

5. **表达上有一点可以更清楚**
   - “用 `[]`”和“用 `()`”可以改成“列表通常用 `[]` 创建,元组通常用 `()` 创建”。
   - 因为严格来说,元组也可以不用括号创建,例如 `1, 2, 3` 也是元组。
   - 但对于初学者,先说“通常用 `()`”更稳妥。

6. **可以提醒单元素元组的特殊写法**
   - 如果面向初学者,是否补充要看答案长度。
   - 单元素元组需要写成:
     ```python
     x = (1,)
     ```
     而不是:
     ```python
     x = (1)
     ```
   - 这不是原问题必须内容,但属于元组学习中常见易错点,可以作为补充说明。

7. **没有明显事实错误**
   - 初稿中关于列表可变、元组不可变、访问元素、修改元组会报错等内容都是正确的。
   - 代码示例也没有问题。

8. **可以更明确说明适用场景**
   - 初稿最后已经总结:
     - list 适合以后可能改变的数据;
     - tuple 适合固定不变的数据。
   - 这很好。
   - 但可以稍微具体化,例如:
     - list:购物车、学生名单、待办事项;
     - tuple:坐标、日期、RGB 颜色值。
   - 这样对初学者更有帮助。

总体来看,初稿已经能正确回答问题,结构清楚、示例简单。主要改进方向是:补充共同点、让例子更生活化、更对称,并在表述上稍微更严谨。

阶段 6:Revise 改进阶段,根据反思意见生成最终答案...

最终答案:
Python 中,**列表(list)**和**元组(tuple)**都可以用来存放多个数据。

它们的共同点:

- 都可以存放多个元素
- 都可以通过下标访问元素
- 都可以用 `for` 循环遍历
- 都可以存放不同类型的数据

主要区别是:**列表可以修改,元组创建后一般不能修改**。

---

## 1. 列表 list

列表通常用 `[]` 创建,适合存放以后可能会变化的数据,比如购物车、学生名单、待办事项。

```python
shopping_cart = ["苹果", "香蕉", "牛奶"]

shopping_cart.append("面包")   # 添加元素
shopping_cart[0] = "橙子"      # 修改元素

print(shopping_cart)
```

输出:

```python
['橙子', '香蕉', '牛奶', '面包']
```

列表就像购物清单,可以随时增加、删除或修改。

---

## 2. 元组 tuple

元组通常用 `()` 创建,适合存放固定不变的数据,比如出生日期、坐标、RGB 颜色值。

```python
birthday = (2000, 5, 20)

print(birthday[0])  # 访问第一个元素
```

输出:

```python
2000
```

如果尝试修改元组中的元素,会报错:

```python
birthday[0] = 1999  # 报错:元组不能修改
```

元组本身是不可变的,不能增加、删除或替换元素。

---

补充一个常见易错点:如果只有一个元素的元组,要写逗号:

```python
x = (1,)   # 这是元组
y = (1)    # 这是整数
```

**一句话总结:列表适合存放会变化的数据,元组适合存放固定不变的数据。**

Reflecton的主要特点

  • 自我检查:主动回看自己的答案,判断有没有逻辑错误、遗漏信息或表达不清的问题
  • 提升结果质量:通过“生成—反思—修改”,通常可以让答案更完整、更准确
  • 可以和其他范式结合:Reflection 可以和 ReAct、Plan-and-Solve 结合使用
方法核心思想侧重点
ReAct边思考边行动工具调用与环境反馈
Plan-and-Solve先规划再执行任务拆解与步骤执行
Reflection先完成再反思自我检查与结果改进

五、智能体框架

快速搭 RAG 或做原型,先用 LangChain,上手最快。需要精确控制 Agent 流程(循环、条件、状态回溯),选 LangGraph,二者可以叠用。多个 Agent 互相协作、需要代码执行或人工审核,AutoGen 更顺手。项目需要分布式部署或团队在国内,可以考虑 AgentScope。只是做研究、需要批量生成对话数据,CAMEL 最省力。

六、记忆与检索

人类智能的一个重要特征就是能够记住过去的经历,从中学习,并将这些经验应用到新的情况中。同样,一个真正智能的智能体也需要具备记忆能力。对于基于LLM的智能体而言,通常面临两个根本性局限:对话状态的遗忘和内置知识的局限

大语言模型虽然强大,但设计上是无状态的。这意味着,每一次用户请求(或API调用)都是一次独立的、无关联的计算。模型本身不会自动“记住”上一次对话的内容

Hello-Agent的记忆系统和RAG系统 记忆系统采用了四层架构设计:

HelloAgents记忆系统
├── 基础设施层 (Infrastructure Layer)
│   ├── MemoryManager - 记忆管理器(统一调度和协调)
│   ├── MemoryItem - 记忆数据结构(标准化记忆项)
│   ├── MemoryConfig - 配置管理(系统参数设置)
│   └── BaseMemory - 记忆基类(通用接口定义)
├── 记忆类型层 (Memory Types Layer)
│   ├── WorkingMemory - 工作记忆(临时信息,TTL管理)
│   ├── EpisodicMemory - 情景记忆(具体事件,时间序列)
│   ├── SemanticMemory - 语义记忆(抽象知识,图谱关系)
│   └── PerceptualMemory - 感知记忆(多模态数据)
├── 存储后端层 (Storage Backend Layer)
│   ├── QdrantVectorStore - 向量存储(高性能语义检索)
│   ├── Neo4jGraphStore - 图存储(知识图谱管理)
│   └── SQLiteDocumentStore - 文档存储(结构化持久化)
└── 嵌入服务层 (Embedding Service Layer)
    ├── DashScopeEmbedding - 通义千问嵌入(云端API)
    ├── LocalTransformerEmbedding - 本地嵌入(离线部署)
    └── TFIDFEmbedding - TFIDF嵌入(轻量级兜底)

RAG系统专注于外部知识的获取和利用:

HelloAgents RAG系统
├── 文档处理层 (Document Processing Layer)
│   ├── DocumentProcessor - 文档处理器(多格式解析)
│   ├── Document - 文档对象(元数据管理)
│   └── Pipeline - RAG管道(端到端处理)
├── 嵌入表示层 (Embedding Layer)
│   └── 统一嵌入接口 - 复用记忆系统的嵌入服务
├── 向量存储层 (Vector Storage Layer)
│   └── QdrantVectorStore - 向量数据库(命名空间隔离)
└── 智能问答层 (Intelligent Q&A Layer)
    ├── 多策略检索 - 向量检索 + MQE + HyDE
    ├── 上下文构建 - 智能片段合并与截断
    └── LLM增强生成 - 基于上下文的准确问答

1、智能体记忆

memory tool

Agent 可以使用的记忆工具接口

  • add:添加记忆
  • search:搜索相关记忆
  • summary:生成记忆摘要
  • stats:查看记忆统计
  • update:更新记忆
  • remove:删除某条记忆
  • forget:按策略遗忘
  • consolidate:记忆整合,比如从工作记忆转为长期记忆

memory manager

tool接收指令, manager负责底层记忆系统的核心逻辑- 记忆存到哪里;

  • 用什么类型存
  • 怎么检索
  • 怎么排序
  • 哪些记忆该遗忘
  • 哪些短期记忆该转为长期记忆
  • 不同用户、不同会话的记忆如何隔离

记忆类型

记忆类型存什么核心作用生命周期存储 / 检索方式
工作记忆当前问题、刚刚说过的话、临时计划、中间结果保存当前任务、当前对话中的临时上下文很短,通常随会话结束或 TTL 到期清理纯内存存储;支持 TTL 自动清理;检索时可结合 TF-IDF 和关键词匹配
情景记忆某次对话、某次任务、某个学习里程碑、某次工具调用结果保存具体发生过的事件和经历较长,可长期保存SQLite + Qdrant;支持时间序列、会话级检索、结构化过滤和语义向量检索
语义记忆用户偏好、长期指令、领域知识、概念关系保存抽象知识、事实、概念、规则长期,稳定性强Qdrant 向量库 + Neo4j 图数据库;支持向量检索和图关系推理
感知记忆图片、音频、代码截图、文件特征、多媒体内容保存多模态感知信息根据重要性和存储空间动态管理按模态分离向量存储;支持同模态和跨模态检索

2、RAG

检索增强生成(Retrieval-Augmented Generation),先查资料再回答问题

  • 无rag:依赖训练时学到的“知识”,可能 过时、不完整,甚至会幻觉
  • 有rag:把外部知识库、文档、数据库、网页等接进来,让模型回答前先检索相关资料,再基于资料生成答案

RAG流程:

  • 原始文档-文档清洗-文本切块 Chunking-转成向量 Embedding-存入向量(文本的语义坐标)数据库
  • 用户问题-问题转向量-去向量数据库检索相似内容-取回相关片段-把片段和问题交给大模型-生成答案

RAG与微调:

方式作用类比
RAG让模型查资料后回答开卷考试
微调 Fine-tuning改变模型的行为、风格或能力训练学生的答题习惯
Prompt临时告诉模型怎么回答考前给答题要求

RAG核心组件:

  • 文档加载器
  • 文本切分器
  • Embedding 模型(将单词转换为向量)
  • 向量数据库
  • 检索器 Retriever
  • 重排序器 Reranker
  • 大模型 LLM
  • 答案生成与引用模块
  • 评估模块

应用场景:

企业知识库问答
客服机器人
文档问答
法律/医疗/金融资料查询
论文助手
代码库问答
个人笔记问答

七、上下文工程

1、noteTool 结构化笔记

NoteTool 是为”长时程任务”提供的结构化外部记忆组件。它以 Markdown 文件作为载体,头部使用 YAML 前置元数据记录关键信息,正文用于记录状态、结论、阻塞与行动项等内容。这种设计结合了人类可读性、版本控制友好性和易于回注上下文的特性,是构建长时程智能体的重要工具。

前面提到的MemoryTool 主要关注对话式记忆——短期工作记忆、情景记忆和语义记忆。对于需要长期追踪、结构化管理的项目式任务,我们需要一种更轻量、更人类友好的记录方式。

NoteTool 填补了这个差距,它提供了:

  • 结构化记录:使用 Markdown + YAML 格式,既适合机器解析,也方便人类阅读和编辑
  • 版本友好:纯文本格式,天然支持 Git 等版本控制系统
  • 低开销:无需复杂的数据库操作,适合轻量级的状态追踪
  • 灵活分类:通过 type 和 tags 灵活组织笔记,支持多维度检索

最佳实践:在实际使用 NoteTool 时,以下最佳实践能帮助您构建更强大的长时程智能体:

  1. 合理的笔记分类:

             - `task_state`:记录阶段性进展和状态
             - `conclusion`:记录重要的结论和发现
             - `blocker`:记录阻塞问题,优先级最高
             - `action`:记录下一步行动计划
             - `reference`:记录重要的参考资料
  2. 定期清理和归档:

             - 对于已解决的 blocker,更新为 conclusion
             - 对于过时的 action,及时删除或更新
             - 使用 tags 进行版本管理,如 `["v1.0", "completed"]`
  3. 与 ContextBuilder 的配合:

             - 在每轮对话前检索相关笔记
             - 根据笔记类型设置不同的相关性分数(blocker > action > conclusion)
             - 限制笔记数量,避免上下文过载
  4. 人机协作:

             - 笔记是人类可读的 Markdown 格式,支持手动编辑
             - 使用 Git 进行版本控制,追踪笔记的演化
             - 在关键阶段,人工审核 Agent 生成的笔记
  5. 自动化工作流:

             - 定期生成笔记摘要报告
             - 基于笔记自动生成项目进度文档
             - 将笔记内容同步到其他系统(如 Notion、Confluence)

2、TerminalTool 文件系统访问

在许多实际场景中,智能体需要即时访问和探索文件系统——查看日志文件、分析代码库结构、检索配置文件等。这就是 TerminalTool 的用武之地。

没有 TerminalTool 时,Agent 可能只能依赖你贴给它的代码,或者依赖提前建好的 RAG 索引。 有 TerminalTool 时,它可以这样逐步探索:

ls -la
find . -name "*.py"
grep -r "login" .
head -n 50 src/auth.py

区别:

工具主要作用类比
RAG从知识库里检索相关片段查资料库
MemoryTool保存长期记忆记住经验
NoteTool保存结构化笔记写项目笔记
TerminalTool实时访问文件系统打开终端查文件

应用场景:

代码库探索
日志分析
数据文件预览
长程智能体任务

八、智能体通讯协议

如果没有通信协议,每接入一个新服务,都要单独写一个 Tool 类。比如接 GitHub 要写 GitHubTool,接数据库要写 DatabaseTool,接天气 API 要写 WeatherTool。这样会带来代码重复、维护困难、无法复用、扩展性差等问题

协议连接对象解决的问题适合场景
MCPAgent ↔ 工具统一调用外部工具文件、数据库、API、GitHub
A2AAgent ↔ Agent多智能体点对点协作研究员 + 撰写员 + 编辑
ANPAgent ↔ Agent 网络服务发现、路由、扩展大规模开放 Agent 网络

IMG-20260603095910964.png | 600

1、MCP(Model Context Protocol)

主要解决: Agent 如何标准化地访问外部工具、资源和服务

MCP 的设计哲学是”上下文共享”。它不仅仅是一个 RPC(远程过程调用)协议,更重要的是它允许智能体和工具之间共享丰富的上下文信息

MCP工作流程: IMG-20260603101059522.png | 500

2、A2A(Agent-to-Agent Protocol)

主要解决:多个 Agent 之间如何直接通信、协作、委托任务

A2A 生命周期: IMG-20260603101653316.png|500

3、ANP(Agent Network Protocol)

主要解决:在一个大规模 Agent 网络中,如何发现、选择、路由合适的 Agent

九、训练与评估 了解

训练:

  •  Agentic RL(基于强化学习的智能体训练)要解决的核心问题。本章将为 HelloAgents 框架引入强化学习训练能力,让你能够训练出具备推理、工具使用等高级能力的智能体
  • 数据集和奖励函数是强化学习训练的两大基石。数据集定义了智能体要学习的任务,奖励函数定义了什么是好的行为。
  • SFT训练,监督微调(Supervised Fine-Tuning, SFT)是强化学习训练的第一步,也是最重要的基础。SFT 让模型学习任务的基本格式、对话模式和初步的推理能力。没有 SFT 的基础,直接进行强化学习往往会失败,因为模型连基本的输出格式都不会。
  • GRPO训练,在完成 SFT 训练后,我们已经得到了一个能够生成结构化答案的模型。但是,SFT 模型只是学会了”模仿”训练数据中的推理过程,并没有真正学会”思考”。强化学习可以让模型通过试错来优化推理策略,从而超越训练数据的质量。

智能体性能评估:

  • 工具调用能力评估:工具调用是智能体的核心能力之一。智能体需要理解用户意图,选择合适的工具,并正确构造函数调用
    • BFCL (Berkeley Function Calling Leaderboard)[1]:UC Berkeley 推出,包含 1120+测试样本,涵盖 simple、multiple、parallel、irrelevance 四个类别,使用 AST 匹配算法评估,数据集规模适中,社区活跃。
  • ToolBench[2]:清华大学推出,包含 16000+真实 API 调用场景,覆盖真实世界的复杂工具使用场景。
  • API-Bank[3]:Microsoft Research 推出,包含 53 个常用 API 工具,专注于评估智能体对 API 文档的理解和调用能力。
  • 通用能力评估:评估智能体在真实世界任务中的综合表现,包括多步推理、知识运用、多模态理解等能力
    • GAIA (General AI Assistants)[4]:Meta AI 和 Hugging Face 联合推出,包含 466 个真实世界问题,分为 Level 1/2/3 三个难度级别,评估多步推理、工具使用、文件处理、网页浏览等能力,使用准精确匹配(Quasi Exact Match)算法,任务真实且综合性强。
  • AgentBench[5]:清华大学推出,包含 8 个不同领域的任务,全面评估智能体的通用能力。
  • WebArena[6]:CMU 推出,评估智能体在真实网页环境中的任务完成能力和网页交互能力。
  • 多智能体协作评估:评估多个智能体协同工作的能力
    • ChatEval[7]:评估多智能体对话系统的质量。
  • SOTOPIA[8]:评估智能体在社交场景中的互动能力。
  • 自定义协作场景:根据具体应用场景设计的评估任务。
  • 常用评估指标:不同基准使用不同的评估指标
    • 准确性指标:Accuracy(准确率)、Exact Match(精确匹配)、F1 Score(F1 分数),用于衡量答案的正确性。
  • 效率指标:Response Time(响应时间)、Token Usage(Token 使用量),用于衡量执行效率。
  • 鲁棒性指标:Error Rate(错误率)、Failure Recovery(故障恢复),用于衡量容错能力。
  • 协作指标:Communication Efficiency(通信效率)、Task Completion(任务完成度),用于衡量协作效果。

搜索文章与随记

输入关键词搜索全部公开内容