DataWhale Hello-Agents 初识智能体:大语言模型与工具调用的完美结合
前言
在人工智能的浪潮中,我们见证了从简单的聊天机器人Chatbot到能够自主思考、调用工具的智能Agent的进化。本文将结合DataWhale的hello-agents教程,带你深入理解Agent的核心原理,并以OpenAI SDK和经典的LLM应用开发框架——Langchain两种方式,演示如何从零开始构建一个智能旅行助手Agent。
一、什么是Agent?Agent与LLM的本质区别
1.1 传统LLM的局限性
传统的大语言模型(LLM)虽然强大,但存在明显的局限性:
- 知识截止:训练数据有时间边界,无法获取实时信息
- 无法行动:只能生成文本,无法与外部世界交互
- 幻觉问题:可能编造不存在的事实
1.2 Agent的突破
Agent通过引入**工具调用(Tool Calling)**能力,让LLM能够:
- 🌐 获取实时信息:通过API调用最新数据,尽可能避免幻觉。
- 🛠️ 执行具体操作:不只是说,而是真正去做,给发达的”大脑“加上手脚
- 🔄 多步推理:通过"思考-行动-观察"的循环解决复杂问题
核心理念:Agent = LLM + 工具(Tools) + 推理循环(Reasoning Loop)
二、Agent的工作原理:ReAct框架
2.1 ReAct范式解析
ReAct(Reasoning + Acting)是Agent设计的核心框架:
- Thought(思考):分析当前状态,决定下一步
- Action(行动):调用特定工具
- Observation(观察):获取工具返回结果
- 循环:回到步骤1,直到完成任务
2.2 系统提示词的设计艺术
一个精心设计的System Prompt至关重要:
AGENT_SYSTEM_PROMPT = """
你是一个智能旅行助手。你的任务是分析用户的请求,并使用可用工具一步步地解决问题。
# 可用工具:
- `get_weather(city: str)`: 查询指定城市的实时天气
- `get_attraction(city: str, weather: str)`: 根据城市和天气搜索推荐的旅游景点
# 行动格式:
你的回答必须严格遵循以下格式。首先是你的思考过程,然后是你要执行的具体行动,每次回复只输出一对Thought-Action:
Thought: [这里是你的思考过程和下一步计划]
Action: [这里是你要调用的工具,格式为 function_name(arg_name="arg_value")]
# 任务完成:
当你收集到足够的信息,能够回答用户的最终问题时,你必须在`Action:`字段后使用 `finish(answer="...")` 来输出最终答案。
"""
设计要点:
- 明确的角色定位(System):告诉AI它是什么,要做什么
- 工具清单(Tools):清晰列出可用工具及其功能
- 输出格式约束(Output):严格的结构化输出要求,便于进一步处理(例如可将大模型返回的信息直接作为参数,交由其他函数模块处理)
- 终止条件(Stop):明确何时结束循环
三、实战演练:从零构建旅行助手Agent
3.1 工具层设计:连接真实世界
工具1:天气查询API集成
def get_weather(city: str) -> str:
"""通过调用 wttr.in API 查询真实的天气信息"""
url = f"https://wttr.in/{city}?format=j1"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
current_condition = data['current_condition'][0]
weather_desc = current_condition['weatherDesc'][0]['value']
temp_c = current_condition['temp_C']
return f"{city}当前天气:{weather_desc},气温{temp_c}摄氏度"
except Exception as e:
return f"错误:查询天气时遇到网络问题 - {e}"
此处返回JSON,并且这种设计便于更换API。
工具2:智能景点推荐
def get_attraction(city: str, weather: str) -> str:
"""根据城市和天气,使用Tavily Search API搜索并返回优化后的景点推荐"""
api_key = os.environ.get("TAVILY_API_KEY")
tavily = TavilyClient(api_key=api_key)
query = f"'{city}' 在'{weather}'天气下最值得去的旅游景点推荐及理由"
try:
response = tavily.search(
query=query,
search_depth="basic",
include_answer=True
)
# Tavily已经为我们总结了搜索结果
if response.get("answer"):
return response["answer"]
# 如果没有总结,则格式化原始结果
formatted_results = []
for result in response.get("results", []):
formatted_results.append(f"- {result['title']}: {result['content']}")
return "根据搜索,为您找到以下信息:\n" + "\n".join(formatted_results)
except Exception as e:
return f"错误:执行Tavily搜索时出现问题 - {e}"
Tavily是专门适配于Agent的搜索引擎,相较于google,baidu等引擎,它更加友好,更加干净,便于配合Agent行为
3.2 推理引擎:LLM作为大脑
兼容多种LLM的客户端设计
class OpenAICompatibleClient:
"""一个用于调用任何兼容OpenAI接口的LLM服务的客户端"""
def __init__(self, model: str="deepseek-chat", base_url: str="https://api.deepseek.com"):
self.model = model
self.client = ChatOpenAI(
model=self.model,
api_key=os.getenv("OPENAI_API_KEY"),
base_url=base_url
)
def generate(self, prompt: str, system_prompt: str):
"""调用LLM API来生成回应"""
try:
full_prompt = f"{system_prompt}\n\n用户: {prompt}"
response = self.client.invoke(input=full_prompt, stream=False)
return response.content
except Exception as e:
return f"错误:调用语言模型服务时出错。"
此处支持任何OpenAI兼容的API(DeepSeek、通义千问等),国产大模型的API更便于调用
3.3 核心循环:思考-行动-观察
# 主循环逻辑
for i in range(5): # 设置最大循环数
print(f"--- 循环 {i+1} ---")
# 1. 构建prompt(包含历史对话)
full_prompt = "\n".join(prompt_history)
# 2. 调用大模型获取决策
llm_output = llm.generate(full_prompt, system_prompt=AGENT_SYSTEM_PROMPT)
# 3. 解析模型输出的Action
action_match = re.search(r"Action: (.*)", llm_output, re.DOTALL)
if not action_match:
print("解析错误:模型输出中未找到 Action。")
break
action_str = action_match.group(1).strip()
# 4. 检查是否完成任务
if action_str.startswith("finish"):
final_answer = re.search(r'finish\(answer="(.*)"\)', action_str).group(1)
print(f"任务完成,最终答案: {final_answer}")
break
# 5. 解析工具调用参数
tool_name = re.search(r"(\w+)\(", action_str).group(1)
args_str = re.search(r"\((.*)\)", action_str).group(1)
kwargs = dict(re.findall(r'(\w+)="([^"]*)"', args_str))
# 6. 执行工具调用
if tool_name in tools:
observation = tools[tool_name](**kwargs)
else:
observation = f"错误:未定义的工具 '{tool_name}'"
# 7. 记录观察结果
observation_str = f"Observation: {observation}"
prompt_history.append(observation_str)
运行示例:
用户输入: 请帮我查询今天纽约的天气,然后根据天气推荐几个合适的旅游景点
--- 循环 1 ---
Thought: 用户需要查询纽约的天气,并根据天气推荐旅游景点。我需要先调用 `get_weather` 工具获取纽约的天气信息。
Action: get_weather(city="纽约")
Observation: 纽约当前天气:晴,气温15摄氏度
--- 循环 2 ---
Thought: 已获取天气信息,现在需要根据晴朗的天气推荐景点。
Action: get_attraction(city="纽约", weather="晴")
Observation: 根据搜索,为您找到以下信息:
- 中央公园:晴天适合散步、野餐
- 自由女神像:户外参观的好时机
- 布鲁克林大桥:天气晴朗,适合步行观景
--- 循环 3 ---
Thought: 已收集足够信息,可以给出最终答案。
Action: finish(answer="纽约今天天气晴朗,气温15度,非常适合户外活动。推荐景点:1)中央公园-适合散步野餐;2)自由女神像-户外参观;3)布鲁克林大桥-步行观景绝佳选择。")
任务完成!
此处有几个设计亮点
关键设计决策:
- 输出截断机制:
# 防止模型输出多个Thought-Action对
match = re.search(
r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)',
llm_output,
re.DOTALL
)
- 安全循环限制:设置最大循环次数防止无限循环
- 错误容错:工具调用失败时继续执行,最终用常识兜底
四、实战优化:使用LangChain框架
手动实现Agent虽然有助于理解原理,但生产环境推荐使用成熟框架。让我们看看如何用LangChain实现相同功能。
4.1 工具定义升级
在国内,为了保证查询的有效性,可以选择国内的天气查询API来构建工具
from langchain_core.tools import StructuredTool
# 将函数包装为LangChain工具
# StructedTool.from_function可将一个函数直接转化为一个Langchain的工具类
attraction_tool = StructuredTool.from_function(
func=get_attraction,
name="get_attraction",
description="根据天气和城市,查询数个推荐的景点"
)
# 使用@tool装饰器定义
@tool
#@tool
def weather_tool(city: str) -> str:
"""
使用和风天气 API 查询指定城市的实时天气。
需要环境变量:
- QWEATHER_API_KEY: 和风天气 API Key
Args:
city: 城市名称,支持中文(如"北京"、"上海")或城市ID
Returns:
天气信息字符串
"""
# 1. 获取 API Key
api_key = os.environ.get("QWEATHER_API_KEY")
if not api_key:
return "错误:未配置 QWEATHER_API_KEY 环境变量。请先在和风天气官网申请 API Key。"
# 2. 城市名称转城市ID(和风天气需要城市ID)
# 使用城市搜索 API 获取城市ID
city_search_url = f"" # 此处的URL可在和风天气控制台中查询
try:
# 搜索城市
search_response = requests.get(city_search_url, timeout=10)
search_response.raise_for_status()
search_data = search_response.json()
# 检查是否找到城市
if search_data.get('code') != '200' or not search_data.get('location'):
return f"错误:未找到城市 '{city}',请检查城市名称是否正确。"
# 获取第一个匹配城市的ID
city_id = search_data['location'][0]['id']
city_name = search_data['location'][0]['name']
# 3. 获取实时天气
weather_url = f"" # 此处url也可以在和风天气控制台中获得
weather_response = requests.get(weather_url, timeout=10)
weather_response.raise_for_status()
weather_data = weather_response.json()
# 检查响应状态
if weather_data.get('code') != '200':
return f"错误:获取天气数据失败 - {weather_data.get('code')}"
# 4. 解析天气数据(虽然用不到这么多,但是都可以先写上,以便于扩展)
now = weather_data['now']
temp = now['temp'] # 温度
feels_like = now['feelsLike'] # 体感温度
text = now['text'] # 天气现象
wind_dir = now['windDir'] # 风向
wind_scale = now['windScale'] # 风力等级
humidity = now['humidity'] # 湿度
obs_time = now['obsTime'] # 观测时间
# 格式化返回结果
result = f"""
🌍 {city_name}当前天气:
☁️ 天气: {text}
🌡️ 温度: {temp}°C (体感 {feels_like}°C)
💨 风向风力: {wind_dir} {wind_scale}级
💧 湿度: {humidity}%
🕐 更新时间: {obs_time}
""".strip()
return result
except requests.exceptions.Timeout as e:
return f"错误:请求超时,网络连接较慢,请稍后重试。- {e}"
except requests.exceptions.RequestException as e:
return f"错误:网络请求失败 - {e}"
except (KeyError, IndexError) as e:
return f"错误:解析天气数据失败 - {e}"
except Exception as e:
return f"错误:未知错误 - {e}"
agent_tools = [weather_tool, attraction_tool]
4.2 模型兼容性
from langchain_openai.chat_models import ChatOpenAI
from langchain_community.chat_models import ChatTongyi
# 支持多种模型
model = ChatTongyi(
model="qwen-max",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 或使用DeepSeek
# model = ChatOpenAI(
# model="deepseek-chat",
# api_key=os.getenv("OPENAI_API_KEY"),
# base_url="https://api.deepseek.com"
# )
4.3 Agent创建与调用
from langchain.agents import create_agent
# 创建Agent
agent = create_agent(model=model, tools=agent_tools)
# 调用Agent
response = agent.invoke({
"messages": [("user", "查询太原的天气并推荐景点")]
})
# 遍历消息流
for msg in response["messages"]:
if msg.type == "human":
print(f"用户: {msg.content}")
elif msg.type == "ai":
if hasattr(msg, 'tool_calls') and msg.tool_calls:
print(f"AI调用工具: {msg.tool_calls[0]['name']}")
else:
print(f"AI回复: {msg.content}")
elif msg.type == "tool":
print(f"工具返回: {msg.content[:100]}...")
4.4 流式输出:更好的用户体验
# 流式调用Agent
inputs = {
"messages": [("user", "查询运城的天气并推荐景点")]
}
for chunk in agent.stream(inputs, stream_mode="updates"):
for node, values in chunk.items():
messages = values["messages"]
for msg in messages:
if msg.type == "ai":
if hasattr(msg, 'tool_calls') and msg.tool_calls:
for tool_call in msg.tool_calls:
print(f"🤖 AI思考: 调用工具 '{tool_call['name']}'")
print(f" 参数: {tool_call['args']}")
elif msg.content:
print(f"🤖 AI回复: {msg.content}")
elif msg.type == "tool":
preview = str(msg.content)[:50] + "..."
print(f"🔧 工具返回: {preview}")
输出效果:
🚀 开始运行 Agent...
🤖 AI思考: 调用工具 'weather_tool'
参数: {'city': '运城'}
🔧 工具返回: 🌍 运城当前天气: ☁️ 天气: 晴 🌡️ 温度: 4°C (体感 -2°C)...
🤖 AI思考: 调用工具 'get_attraction'
参数: {'city': '运城', 'weather': '晴'}
🔧 工具返回: 根据运城晴朗的天气,推荐以下景点...
==============================
🤖 AI回复: 运城今天天气晴朗,气温4度(体感-2度),建议注意保暖...
流式输出的价值:
- ⚡ 实时反馈:用户立即看到Agent的思考过程
- 🎭 透明性:展示决策链路,增强信任感
- 🎯 调试友好:清晰看到每一步的输入输出
五、深度思考:Agent设计的核心挑战与解决方案
5.1 工具调用的可靠性问题
挑战:Agent可能选择错误的工具或参数
解决方案:
- 精确的工具描述:
# ❌ 模糊描述
def get_data(data):
"""获取数据"""
# ✅ 精确描述
def get_weather(city: str) -> str:
"""
查询指定城市的实时天气信息。
Args:
city: 城市名称,支持中文(如"北京")或英文(如"Beijing")
Returns:
包含温度、天气状况、湿度等信息的字符串
"""
- 参数验证:
def get_attraction(city: str, weather: str) -> str:
# 参数校验
if not city or len(city) < 2:
return "错误:城市名称无效"
# 设置默认天气
if not weather:
weather = get_weather(city)
# 业务逻辑...
- 工具输出的标准化:
# 统一错误格式
ERROR_PREFIX = "错误:"
def call_tool(tool_func, **kwargs):
try:
result = tool_func(**kwargs)
if result.startswith(ERROR_PREFIX):
# 记录错误日志
log_error(result)
return result
except Exception as e:
return f"{ERROR_PREFIX}{tool_func.__name__}异常 - {e}"
5.2 幻觉问题的缓解
挑战:Agent可能编造工具调用结果
多层防护策略:
- 工具层验证:
def get_weather(city: str) -> str:
response = requests.get(f"https://wttr.in/{city}?format=j1")
# 验证响应
if response.status_code != 200:
return f"错误:API返回状态码 {response.status_code}"
data = response.json()
# 验证数据完整性
if 'current_condition' not in data:
return "错误:天气数据格式异常"
# 数据提取...
- Prompt工程强化:
SYSTEM_PROMPT = """
# 重要约束:
- 你只能使用列出的工具,不能编造工具
- 如果工具返回错误信息,必须如实反馈给用户
- 不得编造或猜测工具调用的结果
- 当且仅当通过工具获得真实数据后,才能给出最终答案
"""
- 结果溯源:
def get_attraction(city: str, weather: str) -> str:
response = tavily.search(query=query, include_answer=True)
# 添加来源信息
answer = response.get("answer", "")
sources = [r['url'] for r in response.get('results', [])]
return f"{answer}\n\n来源: {', '.join(sources)}"
5.3 性能优化策略
挑战:多次工具调用导致延迟
优化方案:
- 并行调用:
# LangChain自动优化
agent = create_agent(
model=model,
tools=agent_tools,
parallel_tool_calls=True # 允许并行调用独立工具
)
- 智能缓存:
from functools import lru_cache
@lru_cache(maxsize=100)
def get_weather(city: str) -> str:
# 相同城市1小时内只调用一次API
return _call_weather_api(city)
- 流式处理:
# 快速展示部分结果
for chunk in agent.stream(inputs):
# 先返回天气,再返回景点推荐
if "weather" in chunk:
yield chunk["weather"]
if "attractions" in chunk:
yield chunk["attractions"]
六、应用场景拓展:Agent的无限可能
6.1 企业级应用
-
客服Agent:
- 订单查询工具
- 退款处理工具
- FAQ知识库搜索
-
数据分析Agent:
- SQL查询工具
- 数据可视化工具
- 报告生成工具
-
代码助手Agent:
- 代码搜索工具
- 单元测试生成工具
- 文档生成工具
6.2 个人助手
-
学习Agent:
- 论文检索工具
- 概念解释工具
- 习题生成工具
-
生活Agent:
- 日程管理工具
- 菜谱推荐工具
- 健康管理工具
七、未来展望:Agent的发展方向
7.1 多模态Agent
当前Agent主要处理文本,未来将支持:
- 🖼️ 图像理解:视觉问答、图像描述
- 🎵 音频处理:语音交互、音乐生成
- 🎬 视频分析:视频问答、内容理解
7.2 自主学习Agent
- 📚 从反馈中学习:根据用户评价优化策略
- 🔄 动态工具发现:自动发现和注册新工具
- 🧠 知识积累:长期记忆与经验总结
7.3 协作Agent生态系统
- 👥 多Agent协作:专业分工,协同工作
- 🌐 Agent市场:工具和能力的交易市场
- 🔗 跨平台集成:无缝对接各类服务
八、总结与行动建议
核心要点回顾
- Agent = LLM + 工具 + 推理循环
- ReAct框架:思考→行动→观察的闭环
- 三大支柱:
- 精心设计的System Prompt
- 可靠的工具层
- 健壮的执行循环
学习路径建议
初级:
- ✅ 理解ReAct框架原理
- ✅ 手动实现简单Agent
- ✅ 掌握工具定义方法
中级:
- 🔧 使用LangChain等框架
- 🔧 处理错误和边界情况
- 🔧 优化Prompt和工具描述
高级:
- 🚀 设计多Agent系统
- 🚀 实现自定义工具链
- 🚀 构建生产级Agent应用
实战项目建议
-
个人知识库Agent:
- 工具:文档搜索、向量检索、总结生成
- 场景:快速找到并总结个人笔记
-
股票分析Agent:
- 工具:实时行情、财报分析、新闻检索
- 场景:智能投资建议
-
代码审查Agent:
- 工具:代码搜索、静态分析、最佳实践库
- 场景:自动化代码审查
九、资源与延伸阅读
技术文档:
结语
从最初的简单聊天机器人到如今能够自主思考、调用工具的智能Agent,我们正在见证AI技术的质变。Agent不仅仅是技术的进步,更是AI从"对话"到"行动"的关键跨越。
掌握Agent开发,意味着你能够让AI:
- 🔍 获取真实世界的信息
- 🛠️ 执行具体的任务操作
- 🧠 进行复杂的多步推理
这正是构建下一代AI应用的核心能力。
现在就开始你的Agent之旅吧! 🚀
💡 本文所有代码均来自实战项目,已验证可运行。建议读者跟随代码实践,加深理解。
📧 如有问题或建议,欢迎讨论交流!
更多推荐
所有评论(0)