HoRain云--AI Agent 智能体

你可能已经习惯了这样的交互:你问一个问题,AI 给出一个答案。

  • 你让它写一篇文章,它写一篇文章。

  • 你让它翻译一句话,它翻译一句话。

这种模式下,AI 更像一个顾问——它给你建议,但不直接做事。

但如果任务稍微复杂一点呢?比如:帮我查一下明天北京的天气,如果温度超过 25 度,就给我推荐一件短袖衬衫,预算 300 元以内,然后把结果整理成邮件发给我老板。

用普通的对话 AI,你得分成好几步:

  • 第一步:查明天北京天气。

  • 第二步:如果超过 25 度,推荐 300 元以内的短袖衬衫。

  • 第三步:帮我写一封邮件给老板,内容是……。

每一步都需要你手动推进。

而 AI Agent(智能体) 的思路是:你只需要说一遍帮我做这件事,它自己会把剩下的都做完。

它会自动判断需要做什么、调用什么工具、按什么顺序执行、遇到问题怎么调整,直到任务完成。

简单理解:普通 LLM 是军师,帮你出谋划策;AI Agent 是执行者,拿到目标后自己就去把事办了。


什么是 AI Agent

AI Agent 是一个能感知环境、做出决策、采取行动的自主系统。

学术一点的定义是:Agent 是一个位于某环境中的实体,它能通过传感器感知环境,并通过执行器作用于环境,以实现一系列目标。

这个定义听起来有点抽象,让我们用大白话拆解。

Agent vs 普通 LLM

先看一张对比表:

特性普通 LLMAI Agent
交互模式一问一答,用户驱动自主执行,目标驱动
能力边界只有模型内置能力可通过工具扩展能力
记忆会话上下文(有限)短期 + 长期记忆
规划无(或需用户引导)自主任务分解与规划
反馈循环无(单次生成)观察-思考-行动 循环

举个具体例子:"帮我订一张明天从上海去北京的机票,价格 1500 元以内。"

普通 LLM 可能会这样回答:"好的,我可以帮你写一段查询机票的代码,或者告诉你应该去哪个网站查询。"但它不会真的去查机票,更不会帮你下单。

而一个合格的 Agent 会:

  • 1. 理解目标:订明天上海→北京,1500 元以内的机票。

  • 2. 决定行动:我需要调用机票查询 API。

  • 3. 执行行动:调用 API,获取航班列表。

  • 4. 观察结果:获取到 10 个航班,其中 3 个在 1500 元以内。

  • 5. 再次思考:选哪个?可能需要再查一下起降时间,或者询问用户偏好。

  • 6. 继续行动:可能调用另一个 API 查准点率,或者直接给用户推荐选项。

  • 7. 完成任务:帮用户锁定座位,生成订单链接。

看到区别了吗?普通 LLM 给你答案,Agent 给你结果。

Agent 的核心能力

一个完整的 Agent 通常具备以下三大核心能力:

能力说明类比
感知(Perception)获取环境信息和反馈眼睛、耳朵
决策(Decision)思考做什么、怎么做大脑
行动(Action)执行具体操作手、脚
  • 感知,就是 Agent 能看到发生了什么。比如用户发了一条消息、API 返回了一个结果、文件系统里新增了一个文件——这些都是感知的输入。

  • 决策,就是 Agent 基于感知到的信息,决定下一步做什么。是直接回答用户?还是需要调用工具?还是需要把大任务拆成小任务?这都是决策。

  • 行动,就是 Agent 真的去执行决策。调用搜索 API、读取文件、发送邮件、操作数据库——这些都是行动。


Agent 的基本架构

现在我们来看 Agent 的标准"四块拼图":大脑、工具、记忆、规划。

先看一张架构图:

大脑:LLM 作为推理核心

Agent 的大脑通常是一个大语言模型,比如 GPT-4、Claude、Llama 等。

这个 LLM 负责:

  • 1. 理解用户的意图:用户说帮我订票,他真正想要的是什么?

  • 2. 做决策:下一步我应该做什么?

  • 3. 生成工具调用参数:要调用机票查询 API,需要哪些参数?

  • 4. 整理最终答案:现在信息收集够了,怎么给用户一个清晰的总结?

LLM 是 Agent 的核心,但不是全部——就像人脑很重要,但也需要手脚才能做事。

工具:扩展 AI 的能力边界

原生 LLM 有两个明显的局限:

  • 1. 知识截止:它不知道训练结束之后发生的事。

  • 2. 无法交互:它不能直接读文件、查数据库、调用 API。

工具(Tools)就是用来解决这些问题的。

常见的工具类型:

工具类型作用示例
搜索工具获取最新信息Google Search、Bing Search
计算工具做数学运算计算器、Wolfram Alpha
文件操作读写本地文件读取 PDF、写入 CSV
API 调用与外部系统交互订机票、发邮件、查天气
数据库查询存取结构化数据SQL 查询、向量检索
代码执行运行代码解决问题Python REPL、Jupyter

给 Agent 工具,就像给人配了一台电脑——瞬间从只能想变成可以做。

记忆:短期 vs 长期记忆

LLM 原生是失忆的——每一次对话都是全新的,除非你把上下文塞进去。

而 Agent 需要记住很多东西:

  • 短期记忆:刚才用户说了什么?我上一步调用了什么工具?得到了什么结果?

  • 长期记忆:用户的偏好是什么?过去类似任务是怎么处理的?有哪些经验教训?

记忆系统的常见实现方式:

记忆类型存储内容实现方式
短期记忆当前会话的对话历史、中间步骤直接放 LLM 上下文窗口
长期记忆用户偏好、历史任务、知识文档向量数据库 + 相似度检索
摘要记忆压缩后的历史总结让 LLM 定期生成摘要

一个好的记忆系统,能让 Agent 看起来记得你,而不是每次都像第一次见面。

规划:任务分解

复杂任务不会一步完成,Agent 需要能把大目标拆成小步骤。

比如:帮我策划一场 10 人的生日派对。

Agent 可能会这样规划:

  • 1. 先问清楚:预算多少?什么时候?在哪里?主角有什么偏好?

  • 2. 然后:查一下附近的场地。

  • 3. 接着:设计菜单。

  • 4. 再接着:列出采购清单。

  • 5. 最后:生成日程安排表。

规划的常见策略:

  • 链式思考(Chain-of-Thought):一步一步想清楚,先做什么、后做什么。

  • 树状规划(Tree-of-Thought):同时考虑多个可能的路径,选最优的。

  • 反思(Reflection):做完一步后回头看看,有没有问题,需不需要调整。


工具调用:Tool Use / Function Calling

工具调用是 Agent 最基础也是最重要的能力。

让我们从是什么开始讲。

什么是 Function Calling

简单说:Function Calling 就是 LLM 输出一个结构化的 JSON,告诉你它想调用哪个函数、传什么参数。

不是 LLM 真的去执行这个函数——它只是告诉你它想调用,执行的事还得由你来做。

整个流程通常是这样的:

  • 1. 你告诉 LLM:"这里有一些函数可以调用,每个函数的名字、参数、用途是……"

  • 2. 用户发消息:"帮我查一下明天北京的天气。"

  • 3. LLM 回复:"我要调用 get_weather 函数,参数是 city='北京', date='明天'。"

  • 4. 你去调用这个函数(真的去查天气 API),得到结果。

  • 5. 你把结果塞回给 LLM:"刚才的函数调用返回了:温度 25 度,晴天。"

  • 6. LLM 基于这个结果,给用户一个自然语言回答:"明天北京晴天,温度 25 度,很舒适。"

看到了吗?LLM 负责想,你负责执行。

定义工具的 JSON Schema

要让 LLM 调用工具,你得先告诉它有哪些工具可用。

这个告诉的过程,就是用 JSON Schema 描述函数。

来看一个标准的函数定义格式:

实例

# 这是一个典型的工具定义(用 Python 字典表示,最终会转成 JSON)
tool_definition = {
"type": "function",
"function": {
"name": "get_weather", # 函数名
"description": "查询指定城市指定日期的天气", # 函数用途描述,LLM 会看这个
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,比如'北京'、'上海'、'深圳'",
},
"date": {
"type": "string",
"description": "日期,格式为 YYYY-MM-DD,比如'2024-06-18'",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,摄氏度或华氏度,默认摄氏度",
},
},
"required": ["city", "date"], # 必填参数
},
},
}

# 另一个工具:搜索
search_tool = {
"type": "function",
"function": {
"name": "web_search",
"description": "在互联网上搜索最新信息,适合查新闻、实时数据、未知知识",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词或问题",
},
"num_results": {
"type": "integer",
"description": "返回多少条结果,默认 5",
"default": 5,
},
},
"required": ["query"],
},
},
}

# 另一个工具:计算器
calculator_tool = {
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算,支持加减乘除、幂运算等",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,比如'25 * 4 + 10'、'sqrt(16)'",
},
},
"required": ["expression"],
},
},
}

print(f"已定义 {len([tool_definition, search_tool, calculator_tool])} 个工具:get_weather, web_search, calculate")
# 输出:已定义 3 个工具:get_weather, web_search, calculate

关键点:

  • description 非常重要——LLM 就是靠这个描述来理解这个工具是干什么的、什么时候用。

  • 参数也要描述清楚——比如 unit 有 enum 约束,LLM 就知道只能从这两个值里选。

  • required 标出必填项——LLM 会确保这些参数一定有值。

代码实战:给 AI 添加搜索、计算工具

现在让我们写一个完整的、可运行的例子。

为了演示,我们用 Python 模拟 Function Calling 的完整流程。

实例

# ============================================
# 一个简化版的 Agent 工具调用演示
# 不需要真实的 API Key,用模拟数据演示
# ============================================

import json
import random
from typing import Dict, Any, List, Optional


class SimpleAgent:
"""一个简单的 Agent 演示类"""

def __init__(self):
# 注册可用的工具
self.tools = self._define_tools()
# 对话历史(记忆)
self.messages: List[Dict] = []

def _define_tools(self) -> List[Dict]:
"""定义所有可用的工具"""
return [
{
"name": "web_search",
"description": "搜索互联网获取最新信息",
"parameters": {
"query": {"type": "string", "description": "搜索关键词"},
},
"required": ["query"],
},
{
"name": "calculate",
"description": "执行数学计算",
"parameters": {
"expression": {"type": "string", "description": "数学表达式"},
},
"required": ["expression"],
},
{
"name": "get_weather",
"description": "查询天气",
"parameters": {
"city": {"type": "string", "description": "城市名"},
"date": {"type": "string", "description": "日期"},
},
"required": ["city", "date"],
},
]

def _call_tool(self, tool_name: str, parameters: Dict[str, Any]) -> str:
"""(模拟)调用工具并返回结果"""
print(f" [工具调用] {tool_name}({parameters})")

if tool_name == "web_search":
query = parameters["query"]
# 模拟搜索结果
results = {
"runoob": "菜鸟教程(runoob)是一个编程学习网站,提供大量编程教程和实例。",
"2024年6月18日北京天气": "北京 2024-06-18:晴天,25°C,湿度 45%。",
"上海人口": "上海 2024 年常住人口约 2489 万。",
}
return results.get(query, f"搜索结果:未找到'{query}'的精确信息,这是一个模拟结果。")

elif tool_name == "calculate":
expr = parameters["expression"]
try:
# 注意:生产环境不要用 eval,这里只是演示
result = eval(expr, {"__builtins__": None}, {
"sqrt": lambda x: x**0.5,
"pow": pow,
})
return f"计算结果:{expr} = {result}"
except Exception as e:
return f"计算错误:{e}"

elif tool_name == "get_weather":
city = parameters["city"]
date = parameters["date"]
# 模拟天气数据
weathers = ["晴天", "多云", "小雨", "阴天"]
temp = random.randint(15, 35)
return f"{city} {date}:{random.choice(weathers)},{temp}°C"

else:
return f"未知工具:{tool_name}"

def _decide_action(self, user_input: str) -> Dict[str, Any]:
"""
(模拟)LLM 决策过程
实际项目中这里会调用真实的 LLM API
"""
# 这里用简单的规则模拟,真实场景应该用 LLM
if "搜索" in user_input or "查一下" in user_input or "什么是" in user_input:
# 提取搜索词(简单模拟)
query = user_input.replace("搜索", "").replace("查一下", "").replace("什么是", "").strip()
if not query:
query = "runoob"
return {
"action": "call_tool",
"tool": "web_search",
"parameters": {"query": query},
}
elif "计算" in user_input or "等于" in user_input:
# 提取表达式(简单模拟)
return {
"action": "call_tool",
"tool": "calculate",
"parameters": {"expression": "25 * 4 + 10"}, # 示例固定值
}
elif "天气" in user_input:
return {
"action": "call_tool",
"tool": "get_weather",
"parameters": {"city": "北京", "date": "2024-06-18"},
}
else:
return {
"action": "respond",
"content": "好的,我来帮你处理这个问题。" + user_input,
}

def run(self, user_input: str) -> str:
"""运行 Agent 处理用户输入"""
print(f"[用户输入] {user_input}")
self.messages.append({"role": "user", "content": user_input})

# 第一步:决定做什么
decision = self._decide_action(user_input)

if decision["action"] == "call_tool":
# 第二步:调用工具
tool_result = self._call_tool(decision["tool"], decision["parameters"])
self.messages.append({"role": "tool", "content": tool_result})

# 第三步:基于工具结果生成最终回答
# (这里简单拼接,实际应该让 LLM 生成)
final_response = f"根据我的查询:{tool_result}\n\n希望这个信息对你有帮助!"
self.messages.append({"role": "assistant", "content": final_response})
return final_response

else:
# 直接回答
self.messages.append({"role": "assistant", "content": decision["content"]})
return decision["content"]


# ============================================
# 测试这个 Agent
# ============================================

agent = SimpleAgent()

print("=" * 60)
response1 = agent.run("搜索一下 runoob 是什么")
print(f"[Agent 回复]\n{response1}")

print("\n" + "=" * 60)
response2 = agent.run("帮我查 2024-06-18 北京的天气")
print(f"[Agent 回复]\n{response2}")

print("\n" + "=" * 60)
response3 = agent.run("计算一下 25 * 4 + 10 等于多少")
print(f"[Agent 回复]\n{response3}")

这个例子虽然简单,但它展示了 Function Calling 的完整闭环:

理解 → 决策 → 调用工具 → 观察结果 → 生成回答。

在真实项目中,_decide_action这一步会换成真实的 LLM 调用,让模型来决定用什么工具、传什么参数。


ReAct 规划循环

有了工具调用能力后,下一步就是:什么时候停?

简单任务可能一步就完成,但复杂任务往往需要多轮迭代。

这就是 ReAct 框架要解决的问题。

思考 → 行动 → 观察

ReAct 这个名字来自 Reasoning(推理)和 Acting(行动)的结合。

它的核心思想很简单:让 Agent 像人一样,想一步、做一步、看一看,然后重复这个循环,直到任务完成。

来看一张流程图:

每一轮循环包含三个步骤:

步骤英文做什么
思考Thought我现在需要做什么?为什么?
行动Action调用工具执行具体操作
观察Observation记录行动的结果

然后回到思考——根据观察到的结果,决定下一步做什么。

这个循环会一直继续,直到 Agent 判断任务完成了,这时它会跳出循环,给出最终答案。

让我们用一个具体例子来看 ReAct 是怎么工作的:

  • 用户问题:SpaceX 成立于哪一年?它的第一枚成功入轨的火箭叫什么?

  • 思考 1:我需要查找 SpaceX 的创立年份,以及它最早完成轨道级发射的火箭型号。这两个信息可能来自不同来源,最好一次性搜索关键词覆盖两者。

  • 行动 1:调用 web_search,查询"SpaceX 成立时间 第一枚入轨火箭"。

  • 观察 1:搜索结果摘要显示:"SpaceX(太空探索技术公司)成立于 2002 年,由埃隆·马斯克创立。其首枚成功进入地球轨道的火箭是猎鹰1号(Falcon 1),于 2008 年 9 月 28 日第四次试飞成功。"(信息完整,覆盖了两个子问题)

  • 思考 2:搜索结果已经同时给出了成立年份(2002年)和第一枚入轨火箭(猎鹰1号),没有歧义,信息可信。无需额外搜索。

  • 最终回答:SpaceX 成立于 2002 年,它的第一枚成功入轨的火箭是猎鹰1号(Falcon 1),于 2008 年 9 月 28 日完成这一里程碑。

  • 说明:看到了吗?每一步都有明确的"思考"、"行动"、"观察",而且 Agent 会自己判断什么时候停——这里只用了 1 轮行动就收集齐了全部所需信息,因此直接进入最终回答,没有多余的循环。

循环终止条件

ReAct 循环不能无限跑下去,需要终止条件。

常见的终止条件:

终止条件说明示例
任务完成Agent 判断已经收集到足够信息,能够回答用户问题"我找到了答案,现在可以回答了"
达到最大步数为了防止死循环,设定最大轮数(比如 10 步)"已经迭代 10 次了,强制停止"
用户中断用户主动说"停"或"够了""不用查了,我知道了"
确定失败Agent 判断这个任务无法完成"搜索了多次都找不到相关信息"

设计 Agent 时,一定要有"最大步数"这个安全阀门——不然它可能会在一个圈子里打转,永远停不下来。


记忆系统设计

Agent 工作时间长了,就会遇到一个问题:上下文窗口装不下所有对话历史。

这时候就需要一个好的记忆系统。

对话历史:短期记忆

最简单的记忆就是完整保存对话历史。

每次都把之前的所有消息塞给 LLM,这样它就能记住刚才发生了什么。

但这种方式有个明显的缺点:LLM 的上下文窗口是有限的。

比如 GPT-4 是 8K 或 32K,Claude 是 200K——虽大,但也不是无限。

对话短的时候没问题,一旦对话很长,或者每轮调用工具返回的内容很多,很快就装不下了。

向量数据库:长期记忆

当记忆太多、上下文装不下时,一个常用的方案是:把记忆存到向量数据库里,需要的时候"检索"相关的出来。

这个思路叫 RAG(Retrieval-Augmented Generation),在 Agent 里同样适用。

工作流程:

  • 1. 每产生一条记忆(用户说的话、工具返回的结果、Agent 的思考),就转换成一个向量(Embedding)。

  • 2. 把这个向量存到向量数据库里。

  • 3. 当 Agent 需要回忆时,把当前的问题也转换成向量。

  • 4. 在向量数据库里搜索"最相似"的几条记忆。

  • 5. 只把这几条相关记忆放进 LLM 上下文。

这样,即使有一万条历史记忆,也只需要把最相关的那几条取出来用。

摘要压缩记忆

另一种记忆优化方案:不存储完整对话原文,由大模型定期对历史会话生成精简摘要,仅持久保存摘要内容。

原始长对话:

  • 用户:帮我查天气。

  • Agent:查哪个城市?

  • 用户:北京。

  • Agent:哪一天?

  • 用户:明天。

  • ……

压缩后摘要:

用户需要查询北京明日天气,Agent 先后确认城市与日期,用户明确查询时间为明天。

该方式可大幅削减Token开销:原本占用 500 Token的完整对话,仅需 50 Token的摘要即可留存核心信息。

落地项目通常会多方案搭配使用,分层管理会话记忆:

  • 近期最新对话:完整留存,直接送入模型上下文;
  • 久远历史对话:统一生成摘要,以摘要替代原文;
  • 长期关键信息:存入向量数据库,按需检索调取。

主流 Agent 框架

理解了原理后,你可以自己写一个 Agent,也可以用现成的框架。

现在最流行的有这几个:

LangChain Agents

LangChain 是目前最流行的 LLM 应用开发框架,它内置了一套 Agent 系统。

核心概念:

Agent:大脑,决定做什么。

Tools:工具列表。

Toolkits:工具套装(比如把数据库相关的几个工具打包在一起)。

AgentExecutor:执行器,负责跑 ReAct 循环。

LangChain 的优点是生态丰富——现成的工具集成很多,开箱即用。

缺点是有时候显得有点重,抽象层次多,出问题时 debug 不容易。

LlamaIndex Agents

LlamaIndex(原来叫 GPT Index)更侧重数据连接——把你的私有数据(文档、数据库、API)和 LLM 接起来。

它的 Agent 强项是:

  • 1. 查询结构化和非结构化数据。

  • 2. 在多个数据源之间路由。

  • 3. 做文档问答(RAG)。

如果你的 Agent 需要大量和"你的数据"打交道,LlamaIndex 是个好选择。

AutoGPT 原理

AutoGPT 是 2023 年初火起来的一个项目,它的特点是:完全自主。

你给它一个大目标(比如"帮我做一个网站"),它会自己:

  • 1. 生成任务清单。

  • 2. 执行任务。

  • 3. 自我反思。

  • 4. 调整计划。

  • 5. 继续往前推进。

AutoGPT 第一次让很多人见识到了"自主 Agent"的威力,但它也暴露了 Agent 的很多问题:

经常在错误的方向上一路跑到黑,不会及时止损。

容易陷入死循环,重复做同样的事。

成本高——跑一圈可能花掉几十美元 API 费用。

但 AutoGPT 作为一个概念验证,非常有启发性。

CrewAI 多智能体

刚才说的都是单个 Agent,而 CrewAI 的思路是:让多个 Agent 组队协作。

比如你可以定义:

产品经理 Agent:负责理解需求、写 PRD。

工程师 Agent:负责写代码。

评审 Agent:负责 Code Review、提意见。

然后让它们像一个真实团队一样协作——互相讨论、分配任务、交付结果。

多智能体是一个很有前景的方向:单个 Agent 可能会犯错误,但多个 Agent 可以互相纠错、互补。


实战:搭建一个自动信息收集 Agent

现在让我们把前面讲的知识点串起来,写一个真正能用的 Agent。

我们要做的这个 Agent 叫runoob-researcher——给它一个主题,它会自动搜索、整理、生成一份研究报告。

实例

# ============================================
# 实战:自动信息收集 Agent
# ============================================

import json
import time
from typing import Dict, Any, List


class ResearchAgent:
"""
一个简单的研究型 Agent:
给定一个主题,自动搜索相关信息,整理成报告
"""

def __init__(self, max_steps: int = 5):
self.max_steps = max_steps
self.memory: List[Dict] = [] # 记忆
self.known_facts: List[str] = [] # 收集到的事实
self.queries_used: List[str] = [] # 用过的搜索词

def _search_web(self, query: str) -> str:
"""(模拟)网络搜索"""
print(f" 搜索:{query}")
time.sleep(0.5) # 模拟延迟

# 模拟知识库
knowledge_base = {
"人工智能": "人工智能(AI)是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。",
"机器学习": "机器学习是人工智能的一个子集,它使计算机能够从数据中学习,而无需明确编程。",
"深度学习": "深度学习是机器学习的一个子集,基于多层神经网络,特别擅长处理图像、文本等非结构化数据。",
"大语言模型": "大语言模型(LLM)是一种基于 Transformer 架构的 AI 模型,经过海量文本训练,能够理解和生成人类语言。",
"Agent": "AI Agent 是一种能自主感知环境、做出决策并采取行动的系统,通常由 LLM、工具、记忆、规划四部分组成。",
"Transformer": "Transformer 是 Google 2017 年提出的一种神经网络架构,是现代 LLM 的基础,核心是注意力机制。",
"runoob": "菜鸟教程(runoob)创建于 2013 年,是一个专注于编程教程的网站,提供多种编程语言的学习资源。",
}

# 查找最相关的知识
for key, value in knowledge_base.items():
if key in query:
return value

return f"关于'{query}'的搜索结果:这是一个非常有趣的主题,涉及多个技术领域。(模拟结果)"

def _think(self, step: int, user_topic: str) -> Dict[str, Any]:
"""
思考下一步做什么
(真实项目中这里应该调用 LLM)
"""
# 简单的启发式规则模拟思考过程
if step == 1:
# 第一步:先搜索主题本身
return {
"thought": f"我需要先了解'{user_topic}'的基本定义和背景。",
"action": "search",
"query": user_topic,
}

elif step == 2:
# 第二步:搜索相关技术
return {
"thought": "基本定义有了,现在我需要搜索相关的技术和概念。",
"action": "search",
"query": f"{user_topic} 相关技术",
}

elif step < self.max_steps:
# 中间步骤:继续深入
return {
"thought": "让我再搜索一些更多维度的信息,让报告更丰富。",
"action": "search",
"query": f"{user_topic} 应用场景",
}

else:
# 最后一步:整理报告
return {
"thought": "我已经收集了足够的信息,现在可以整理最终报告了。",
"action": "finish",
}

def _observe(self, action_result: str):
"""观察结果,记录到记忆"""
self.known_facts.append(action_result)
self.memory.append({
"role": "observation",
"content": action_result,
})

def _generate_report(self, topic: str) -> str:
"""基于收集到的信息生成报告"""
report = f"# {topic} 研究报告\n\n"
report += f"> 本报告由 runoob-researcher 自动生成\n\n"
report += "---\n\n"
report += "## 核心要点\n\n"

for i, fact in enumerate(self.known_facts, 1):
report += f"{i}. {fact}\n\n"

report += "---\n\n"
report += "## 总结\n\n"
report += f"以上就是关于'{topic}'的主要信息整理。"
report += "如需更深入的研究,可以进一步搜索相关学术论文或技术文档。\n"

return report

def run(self, topic: str) -> str:
"""运行研究 Agent"""
print(f" runoob-researcher 启动!")
print(f" 研究主题:{topic}")
print("-" * 50)

for step in range(1, self.max_steps + 1):
print(f"\n 第 {step} 步 / 共 {self.max_steps} 步")

# 1. 思考
decision = self._think(step, topic)
print(f" 思考:{decision['thought']}")

if decision["action"] == "finish":
print(" 收集完成,开始整理报告...")
break

# 2. 行动
if decision["action"] == "search":
result = self._search_web(decision["query"])
print(f" 结果:{result[:60]}...")

# 3. 观察
self._observe(result)

# 生成最终报告
print("\n" + "-" * 50)
print(" 生成研究报告...")
final_report = self._generate_report(topic)
return final_report


# ============================================
# 试试看效果
# ============================================

agent = ResearchAgent(max_steps=4)
report = agent.run("Agent 智能体")

print("\n" + "=" * 50)
print(report)
print("=" * 50)

这个 Agent 虽然简化了 LLM 调用(用规则模拟),但整个流程是真实可用的。

你只需要把_think方法换成真实的 LLM API 调用,把_search_web换成真实的搜索 API,它就能真的帮你做研究了。


Agent 的局限与失败模式

AI Agent 很强大,但它不是万能的。

了解它的局限,能帮助你在合适的场景用它,也能避免踩坑。

常见的失败模式

1. 工具选择错误

用户问天气,Agent 去调用搜索;用户要搜索,Agent 去调用计算器。

这种错误很常见,特别是在工具定义不够清晰的时候。

2. 参数传递错误

日期格式不对、缺少必填参数、参数类型传错——这些都会导致工具调用失败。

3. 无限循环

Agent 陷入"搜索 A → 没找到 → 再搜索 A → 还是没找到 → 又搜索 A……"的死循环。

4. 过早停止

还没收集到足够信息,Agent 就觉得"够了",给出一个不完整的答案。

5. 幻觉(Hallucination)

Agent 可能会编造根本不存在的工具、不存在的参数,或者编造假的搜索结果。

6. 缺乏常识

有时候 Agent 会做一些人类看来很傻的事——比如用户说"帮我订明天的机票", Agent 去查"明天"是哪一天,但其实它应该用今天的日期加一天。

怎么让 Agent 更可靠

面对这些问题,业界有一些常用的 mitigation(缓解)策略:

策略解决什么问题怎么做
Human-in-the-loop避免 Agent 走错路关键步骤让人类确认,而不是完全自主
更清晰的工具定义工具选择错误description 写得越详细越好,给出示例
重试 + 容错工具调用失败失败时自动重试,或者给 LLM 明确的错误反馈
强制输出格式参数解析失败用 JSON Schema 约束,或者用 Output Parser
反思机制死循环让 Agent 定期回顾:"我是不是在重复做无用功?"

一个实用的建议:不要一开始就追求完全自主。先从人机协作模式开始——Agent 提议做什么,人类确认后再执行。等你对它有信心了,再逐步放权。